Skip to content

Let's Encrypt#

In this tuto, we will generate SSL certificats with script

Start web server#

  • Create directories and go to proxy directory (we will stay here):
    1
    2
    mkdir -p proxy/{config,certbot,ssl}
    cd proxy
    
  • Create default web config file
    • location /.well-known/acme-challenge/ will handle Let's Encrypt verification requests
    • location / will redirect all HTTP request to HTTPS

config/default.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    listen 80;
    server_name _;
    server_tokens off;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        if ($http_x_forwarded_proto = 'http') {
            return 301 https://$host$request_uri;
        }
    }

    access_log  /var/log/nginx/access.log  main;
}

  • Create proxy directory:
    1
    mkdir -p proxy
    

config/default.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    listen 80;
    server_name _;
    server_tokens off;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        if ($http_x_forwarded_proto = 'http') {
            return 301 https://$host$request_uri;
        }
    }

    access_log  /var/log/nginx/access.log  main;
}
proxy/haproxy.cfg:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
global
    default-path config
    zero-warning
    user haproxy
    group haproxy
    hard-stop-after 5m
    log stdout format raw local0 info

defaults http
    mode http
    option httplog
    log global
    timeout client 1m
    timeout server 1m
    timeout connect 10s
    timeout http-keep-alive 2m
    timeout queue 15s
    timeout tunnel 4h  # for websocket

frontend ft_http
    bind :80 name clear
    http-request set-header X-Forwarded-Proto http
    http-request add-header X-Forwarded-For %[src]

    use_backend default if { path_beg -i /.well-known/acme-challenge }

    default_backend default

backend default
    server container_web web:80 check

  • Create docker-compose file docker-compose.yml:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
---
version: '3.8'
services:
    web:
        image: nginx:1.17-alpine
        ports:
        -   "80:80"
        -   "443:443"
        volumes:
        - "./config:/etc/nginx/conf.d"
        - "./certbot:/var/www/certbot"
        - "./ssl:/etc/nginx/ssl"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
---
version: '3.8'
services:
    proxy:
        image: haproxy:2.4
        depends_on:
        - web
        ports:
        -   "80:80"
        -   "443:443"
        volumes:
        - "./proxy:/usr/local/etc/haproxy"
    web:
        image: nginx:1.17-alpine
        volumes:
        - "./config:/etc/nginx/conf.d"
        - "./certbot:/var/www/certbot"
        - "./ssl:/etc/nginx/ssl"
  • Start web server:
1
docker-compose up -d

Generate SSL certificates#

We will create a script who will: * Generate dhparam.pem * Read config to find Server Name * Test certificats validity and renew if needed * Copy certificats to ssl directory

  • create SSL VHOST file config/test.sile.tech.conf
    • replace test.sile.tech with you domain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    listen 443 http2 ssl;
    server_name test.sile.tech;

    ssl_certificate     /etc/nginx/ssl/test.sile.tech_fullchain.crt;
    ssl_certificate_key /etc/nginx/ssl/test.sile.tech_privkey.crt;
    ssl_dhparam         /etc/nginx/ssl/dhparam.pem;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

proxy/haproxy.cfg:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
global
    default-path config
    zero-warning
    user haproxy
    group haproxy
    hard-stop-after 5m
    log stdout format raw local0 info

defaults http
    mode http
    option httplog
    log global
    timeout client 1m
    timeout server 1m
    timeout connect 10s
    timeout http-keep-alive 2m
    timeout queue 15s
    timeout tunnel 4h  # for websocket

frontend ft_http
    bind :80 name clear
    http-request set-header X-Forwarded-Proto http
    http-request add-header X-Forwarded-For %[src]

    use_backend default if { path_beg -i /.well-known/acme-challenge }

    default_backend default

frontend ft_https
    bind :443 tfo ssl no-sslv3 crt /usr/local/etc/haproxy/ssl alpn h2,http/1.1
    http-request set-header X-Forwarded-Proto http
    http-request add-header X-Forwarded-For %[src]

    acl vhost_test hdr(host) -i test.sile.tech

    use_backend bk_test if vhost_test

    default_backend default

backend default
    server container_web web:80 check

backend bk_test
    server container_web web:80 check

config/test.sile.tech.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    listen 80;
    server_name test.sile.tech;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

  • Create script generate_certificates.sh:

    • Replace __MAIL__ with your mail address
    • Configure SERVER_TYPE with your configuration
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      #!/usr/bin/env bash
      
      cd `dirname $0`
      
      # Directories
      BASE_DIR="$PWD"
      CERTBOT_DIR="$BASE_DIR/certbot"
      CONFIG_DIR="$BASE_DIR/config"
      LETS_ENCRYPT_DIR="$BASE_DIR/letsencrypt"
      SSL_DIR="$BASE_DIR/ssl"
      PROXY_SSL_DIR="$BASE_DIR/proxy/ssl"
      
      # Config
      SERVER_TYPE=haproxy
      RSA_KEY_SIZE=4096
      MAIL_ADDRESS="__MAIL__"
      
      log() {
          echo "[`date`] $@"
      }
      
      renew_ssl() {
          domain=$1
          log "Generate SSL for $domain ..."
          docker run --rm \
              -v "$LETS_ENCRYPT_DIR:/etc/letsencrypt" \
              -v "$CERTBOT_DIR:/var/www/certbot" \
              certbot/certbot  \
                  certonly --webroot -w /var/www/certbot \
                  -n \
                  --email $MAIL_ADDRESS \
                  -d $domain \
                  --rsa-key-size $RSA_KEY_SIZE \
                  --agree-tos \
                  --force-renewal
          certbot_ret=$?
          if [ $certbot_ret -ne 0 ] ; then
              log "Fail to create cert"
              exit 1
          fi
      }
      
      mkdir -p "$LETS_ENCRYPT_DIR" "$SSL_DIR"
      
      if ! [ -f "$SSL_DIR/dhparam.pem" ] ; then
          log 'Create $SSL_DIR/dhparam.pem'
          openssl dhparam -out "$SSL_DIR/dhparam.pem" 2048
      fi
      
      log 'Scan configuration'
      case $SERVER_TYPE in
          nginx )
              domains=$(cat $CONFIG_DIR/* | grep server_name | awk '{print $2}' | cut -d\; -f1 | grep -v _ | sort | uniq)
              ;;
          haproxy )
              domains=$(cat proxy/* | grep 'hdr(host)' | awk '{print $5}' | sort | uniq)
              ;;
      esac
      
      log "Found `echo $domains | wc -w` vhosts: $domains"
      
      log 'Start SSl Creation'
      for domain in $domains ; do
          sudo ls $LETS_ENCRYPT_DIR/live/$domain/cert.pem >/dev/null 2>&1
          file_exist=$?
          if [ $file_exist -eq 0 ] ; then
              log "Test cert '$domain'"
              notAfter=$(sudo openssl x509 -noout -in $LETS_ENCRYPT_DIR/live/$domain/cert.pem -dates | grep notAfter | cut -d= -f2)
              expireDate=$(date -d "$notAfter" "+%s")
              limitDate=$(date --date="2 weeks" "+%s")
              if [ $expireDate -lt $limitDate ] ; then
                  log "Cert '$domain' expire soon, run renew"
                  renew_ssl $domain
              else
                  log "Cert '$domain' OK, do nothing"
              fi
          else
              renew_ssl $domain
          fi
      done
      
      for domain in `sudo ls $LETS_ENCRYPT_DIR/live/ | grep -v README` ; do
          case $SERVER_TYPE in
              nginx )
                  for name in cert chain fullchain privkey ; do
                      sudo cp "${LETS_ENCRYPT_DIR}/live/${domain}/${name}.pem" "${SSL_DIR}/${domain}_${name}.crt"
                  done
                  ;;
              haproxy )
                  sudo cat "${LETS_ENCRYPT_DIR}/live/${domain}/fullchain.pem" "${LETS_ENCRYPT_DIR}/live/${domain}/privkey.pem" \
                      > "$PROXY_SSL_DIR/${domain}.pem"
                  ;;
          esac
      done
      
      sudo chown -R $USER: "$SSL_DIR"
      
  • Test and Reload service:

1
docker-compose exec web sh -c "nginx -t && nginx -s reload"
1
2
3
docker-compose exec web sh -c "nginx -t && nginx -s reload"
docker-compose exec proxy sh -c "haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg" \
    && docker-compose kill -s SIGHUP proxy