Updated 29.04.2020 : Code updated

Setting up Let's Encrypt on AWS EB can be a little bit tricky. In this post I'm going to explain you how to configure your environment correctly for one or several domains / subdomains.

.ebextensions folder

Ebextensions folder contains the configuration of your AWS EB application. This folder contains .config files. This folder is NOT inside your Docker image. Just create the .ebextensions folder in the root folder of your git repository :

➜  webapp git:(master) ✗ ls -la
drwxr-xr-x  20 michaelcaraccio  admin    640 Apr 10 01:17 .
drwxrwxr-x  12 michaelcaraccio  admin    384 Mar 30 22:48 ..
drwxr-xr-x   4 michaelcaraccio  admin    128 Jul  1  2019 .ebextensions
drwxr-xr-x  15 michaelcaraccio  admin    480 Apr 29 14:19 .git
-rw-r--r--@  1 michaelcaraccio  admin   4314 Jul 23  2019 Dockerfile
drwxr-xr-x  28 michaelcaraccio  admin    896 Mar 24 15:16 laravel

If you need more information about this folder, take a look at the documentation.

For this example, we create a new file named : AWS_letsencrypt_config.config.

➜  webapp git:(master) ✗ ls -la .ebextensions
total 24
drwxr-xr-x   4 michaelcaraccio  admin   128 Jul  1  2019 .
drwxr-xr-x  20 michaelcaraccio  admin   640 Apr 10 01:17 ..
-rw-r--r--@  1 michaelcaraccio  admin  4710 Mar 27 19:23 AWS_letsencrypt_config.config

In this example, I have one domain and two subdomains :

  • mywebsite.com
  • admin.mywebsite.com
  • test.mywebsite.com

Step 1 : Configure the security groups

This step will allow traffic on port 443 (SSL). Don't forget to open port 443 on your docker image if you have an existing instance.

Resources:
  sslSecurityGroupIngress: 
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      CidrIp: 0.0.0.0/0

Step 2 : Configure Nginx

Now, we are going to create two configuration files.

The first file (000_http_redirect_custom.conf), will tell to the Nginx server to listen to any requests that come on port 80 (default HTTP) and redirect them to HTTPS.

files:
  /etc/nginx/conf.d/000_http_redirect_custom.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      server {
        listen 80;
        return 301 https://$host$request_uri;
      }

The second file (https_custom.pre), is about the SSL configuration : files location for our certificates and proxy.

/etc/nginx/conf.d/https_custom.pre:
    mode: "000644"
    owner: root
    group: root
    content: |
      # HTTPS server
      server {
        listen       443 default ssl;
        server_name  localhost;
        error_page  497 https://$host$request_uri;
        
        ssl_certificate      /etc/letsencrypt/live/ebcert/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/ebcert/privkey.pem;

        ssl_session_timeout  5m;
        ssl_protocols  TLSv1.1 TLSv1.2;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_prefer_server_ciphers   on;

        location / {
          proxy_pass http://docker;
          proxy_http_version 1.1;

          proxy_set_header Connection "";
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
      }

Step 3 : Container configuration

packages : package option to grab EPEL (Extra Packages for Enterprise Linux) for our certbot.

container_commands : the following code is divided in 6 steps :

  1. 00_create_dir : create a directory for certbot
  2. 10_installcertbotandchmod : download certbot-auto and give permission
  3. 20_getcert : generate certificate
  4. 30_link : create a symbolic link between our certificate and the live configuration directory
  5. 40_config : certificates are now created, so we can rename the .pre to .config
  6. 50_cronjobsetrenewal : create a cron job

Don't forget to set the EMAIL variable of your environment configuration (from your AWS Console).

packages: 
  yum:
    epel-release: []

container_commands:
  00_create_dir:
    command: "mkdir -p /opt/certbot"
  10_installcertbotandchmod:
    command: "wget https://dl.eff.org/certbot-auto -O /opt/certbot/certbot-auto;chmod a+x /opt/certbot/certbot-auto"
  20_getcert:
    command: "sudo /opt/certbot/certbot-auto certonly --standalone --debug --non-interactive --email ${EMAIL} --agree-tos --domains mywebsite.com \
    -d admin.mywebsite.com \
    -d test.mywebsite.com \
    --expand --renew-with-new-domains --pre-hook \"service nginx stop\""
  30_link:
    command: "sudo ln -sf /etc/letsencrypt/live/mywebsite.com /etc/letsencrypt/live/ebcert"
  40_config:
    command: "mv /etc/nginx/conf.d/https_custom.pre /etc/nginx/conf.d/https_custom.conf"
  50_cronjobsetrenewal:
    command: '(crontab -l ; echo ''0 6 * * * root /opt/certbot/certbot-auto renew --standalone --pre-hook "service nginx stop" --post-hook "service nginx start" --force-renew'') | crontab -'

That's it! Commit your changes and deploy it : eb deploy


Complete code :

Resources:
  sslSecurityGroupIngress: 
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      CidrIp: 0.0.0.0/0

files:
  /etc/nginx/conf.d/000_http_redirect_custom.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      server {
        listen 80;
        return 301 https://$host$request_uri;
      }

  /etc/nginx/conf.d/https_custom.pre:
    mode: "000644"
    owner: root
    group: root
    content: |
      # HTTPS server
      server {
        listen       443 default ssl;
        server_name  localhost;
        error_page  497 https://$host$request_uri;
        
        ssl_certificate      /etc/letsencrypt/live/ebcert/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/ebcert/privkey.pem;

        ssl_session_timeout  5m;
        ssl_protocols  TLSv1.1 TLSv1.2;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_prefer_server_ciphers   on;

        location / {
          proxy_pass http://docker;
          proxy_http_version 1.1;

          proxy_set_header Connection "";
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
      }

packages: 
  yum:
    epel-release: []

container_commands:
  00_create_dir:
    command: "mkdir -p /opt/certbot"
  10_installcertbotandchmod:
    command: "wget https://dl.eff.org/certbot-auto -O /opt/certbot/certbot-auto;chmod a+x /opt/certbot/certbot-auto"
  20_getcert:
    command: "sudo /opt/certbot/certbot-auto certonly --standalone --debug --non-interactive --email ${EMAIL} --agree-tos --domains mywebsite.com \
    -d admin.mywebsite.com \
    -d test.mywebsite.com \
    --expand --renew-with-new-domains --pre-hook \"service nginx stop\""
  30_link:
    command: "sudo ln -sf /etc/letsencrypt/live/mywebsite.com /etc/letsencrypt/live/ebcert"
  40_config:
    command: "mv /etc/nginx/conf.d/https_custom.pre /etc/nginx/conf.d/https_custom.conf"
  50_cronjobsetrenewal:
    command: '(crontab -l ; echo ''0 6 * * * root /opt/certbot/certbot-auto renew --standalone --pre-hook "service nginx stop" --post-hook "service nginx start" --force-renew'') | crontab -'

Photo by Paul Esch-Laurent on Unsplash

Sources :

  1. https://matthewoden.com/setting-up-ssl-on-elastic-beanstalk/
  2. https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html
  3. https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-with-ssl-as-a-reverse-proxy-for-jenkins
  4. https://gist.github.com/tony-gutierrez/198988c34e020af0192bab543d35a62a