Install HAProxy:

Latest stable 1.6:

  • sudo add-apt-repository ppa:vbernat/haproxy-1.6
  • sudo apt-get update
  • sudo apt-get install haproxy

Haproxy plugin for webroot validation:

I used this haproxy plugin to be able to serve simple files so that I can use the "webroot" validation protocol of the letsencrypt client:

https://github.com/janeczku/haproxy-acme-validation-plugin

So I just download the .lua file into my /etc/haproxy/:

sudo wget https://raw.githubusercontent.com/janeczku/haproxy-acme-validation-plugin/master/acme-http01-webroot.lua /etc/haproxy  

then I add the following to my /etc/haproxy/haproxy.cfg:

global  
       lua-load /etc/haproxy/acme-http01-webroot.lua

frontend http  
    acl url_acme_challenge path_beg /.well-known/acme-challenge/
    http-request use-service lua.acme-http01 if METH_GET url_acme_challenge

Letsencrypt client:

https://github.com/letsencrypt/letsencrypt

git clone https://github.com/letsencrypt/letsencrypt  
cd letsencrypt  

Now I can run my letsencrypt command:
(during test add --test-cert --break-my-certs)

sudo ./letsencrypt-auto certonly --text --webroot --webroot-path /var/lib/haproxy --renew-by-default --agree-tos --email hedefalk@gmail.com -d woodenstake.se -d jenkins.woodenstake.se -d jenkins-nas.woodenstake.se -d repo.woodenstake.se -d blog.woodenstake.se -d transmission.woodenstake.se -d uniplybeta.woodenstake.se -d crm.woodenstake.se -d docker.woodenstake.se  

So this puts challenge response files in /var/lib/haproxy that the lua-plugin then serves to the letsencrypt requests coming in, proving that I own the domains.

Concat the SAN cert:

sudo cat /etc/letsencrypt/live/repo.woodenstake.se/privkey.pem /etc/letsencrypt/live/repo.woodenstake.se/fullchain.pem | sudo tee /etc/letsencrypt/live/repo.woodenstake.se/haproxy.pem >/dev/null  

and reload haproxy:

sudo service haproxy reload  

Now all my address bars are green, yey!

Caveats:

TODO:

  • cronjob to update my certs

For completeness, here's almost my entire haproxy.cfg at the point when I publish this:

global  
    log /dev/log    local0
    log /dev/log    local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

    crt-base /etc/letsencrypt/live


    # Default ciphers to use on SSL-enabled listening sockets.
    # For more information, see ciphers(1SSL). This list is from:
    #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
    ssl-default-bind-options no-sslv3

    tune.ssl.default-dh-param 2048

    lua-load /etc/haproxy/acme-http01-webroot.lua

defaults  
    log global
    mode    http    

    option  httplog
        timeout connect 5000
        timeout client  50000
        timeout server  50000



frontend http  
    mode http
    bind *:80
    option httplog


    # Letsencrypt: https://github.com/janeczku/haproxy-acme-validation-plugin
    acl url_acme_challenge path_beg /.well-known/acme-challenge/
    http-request use-service lua.acme-http01 if METH_GET url_acme_challenge

    redirect scheme https code 301 if { hdr(Host) -i repo.woodenstake.se } !{ ssl_fc }
    redirect scheme https code 301 if { hdr(Host) -i jenkins.woodenstake.se } !{ ssl_fc }

    acl host_blog hdr(host) -i blog.woodenstake.se

    use_backend ghost if host_blog !url_acme_challenge

frontend https  
    mode http

    bind *:443 ssl crt /etc/letsencrypt/live/repo.woodenstake.se/haproxy.pem




    # Define hosts based on domain names
    acl host_jenkins hdr(host) -i jenkins.woodenstake.se
    acl host_repo hdr(host) -i repo.woodenstake.se
    acl host_jenkins_nas hdr(host) -i jenkins-nas.woodenstake.se
    acl host_transmission hdr(host) -i transmission.woodenstake.se
    acl host_blog hdr(host) -i blog.woodenstake.se
    acl host_docker hdr(host) -i docker.woodenstake.se


    use_backend jenkins if host_jenkins
    use_backend nexus if host_repo
    use_backend jenkins_nas if host_jenkins_nas
    use_backend transmission_nas if host_transmission
    use_backend ghost if host_blog
    use_backend docker if host_docker

backend jenkins  
    mode http
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server jenkins_backend 127.0.0.1:8080

backend nexus  
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server nexus_backend 127.0.0.1:8081

backend jenkins_nas  
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server jenkins_nas_backend 10.0.1.30

backend transmission_nas  
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server jenkins_nas_backend 10.0.1.30:8181

backend ghost  
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server ghost_docker 127.0.0.1:2368

backend docker  
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    server docker_registry 127.0.0.1:5000

Update 20170406:

Installed certbot and now:

sudo certbot certonly --text --webroot --webroot-path /var/lib/haproxy --agree-tos --email hedefalk@gmail.com -d woodenstake.se -d jenkins.woodenstake.se -d jenkins-nas.woodenstake.se -d repo.woodenstake.se -d blog.woodenstake.se -d transmission.woodenstake.se -d uniplybeta.woodenstake.se -d crm.woodenstake.se -d docker.woodenstake.se -d drone.github.woodenstake.se -d drone.gitlab.woodenstake.se

sudo cat /etc/letsencrypt/live/transmission.woodenstake.se/privkey.pem /etc/letsencrypt/live/transmission.woodenstake.se/fullchain.pem | sudo tee /etc/letsencrypt/live/woodenstake.se/haproxy.pem > /dev/null

sudo haproxy reload  

to renew.

About the Author

Viktor Hedefalk

Functional programming devotee. DIY complex.