Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
AWS Elastic Beanstalk .ebextensions config for single instance free SSL using letsencrypt certbot and nginx. http://bluefletch.com/blog/domain-agnostic-letsencrypt-ssl-config-for-elastic-beanstalk-single-instances/
# Dont forget to set the env variable "certdomain", and either fill in your email below or use an env variable for that too.
# Also note that this config is using the LetsEncrypt staging server, remove the flag when ready!
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:
# The Nginx config forces https, and is meant as an example only.
/etc/nginx/conf.d/000_http_redirect_custom.conf:
mode: "000644"
owner: root
group: root
content: |
server {
listen 8080;
return 301 https://$host$request_uri;
}
# The Nginx config forces https, and is meant as an example only.
/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;
if ($ssl_protocol = "") {
rewrite ^ https://$host$request_uri? permanent;
}
location ~ ^/(lib/|img/) {
root /var/app/current/public;
access_log off;
}
location / {
proxy_pass http://nodejs;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
packages:
yum:
epel-release: []
container_commands:
10_installcertbot:
command: "wget https://dl.eff.org/certbot-auto;chmod a+x certbot-auto"
20_getcert:
command: "sudo ./certbot-auto certonly --debug --non-interactive --email XXX@XXX.com --agree-tos --standalone --domains ${certdomain} --keep-until-expiring --pre-hook \"service nginx stop\" --staging"
30_link:
command: "ln -sf /etc/letsencrypt/live/${certdomain} /etc/letsencrypt/live/ebcert"
40_config:
command: "mv /etc/nginx/conf.d/https_custom.pre /etc/nginx/conf.d/https_custom.conf"
@kikar

This comment has been minimized.

Copy link

@kikar kikar commented Jul 7, 2016

Hey Tony, this is amazing, exactly what I was looking for.
So all I have to do is:

  1. create a folder .ebextensions and stick this file in it
  2. Set enviroment variable in EBS configuration called 'certdomain'
  3. Replace the email XXX@XXX.com in the script
  4. Remove --staging flag
    Correct?
@tony-gutierrez

This comment has been minimized.

Copy link
Owner Author

@tony-gutierrez tony-gutierrez commented Jul 11, 2016

More or less correct. See my reply to your comment on the original blogpost. Also I found that using --standalone now works on AWS and is more reliable. See latest version of GIST.

@Giaco9

This comment has been minimized.

Copy link

@Giaco9 Giaco9 commented Jul 12, 2016

Very nice Gist!
I'm using it on AWS Elastic Beanstalk single instance, nodeJS and socket.io and I need ssl. Over http everything works fine, on https socket.io always downgrade to polling transport, googling it i found only this link, could be a solution for my problem? Can you help me?

Thanks a lot!

@williamdias

This comment has been minimized.

Copy link

@williamdias williamdias commented Jul 15, 2016

Did you have any success running this configuration during the elastic beanstalk creation? It seems that neither nginx nor puma (in my case) are running when certbot tries to validate the domain and get the certificate. I tried to create a shell script on /opt/elasticbeanstalk/hooks/appdeploy/post directory to perform all these configurations after the EBS setup but it didn't work either. Certbot can't get a response from the server (I tried forcing validation on ports 80 and 443). Nonetheless, if you are performing a simple deploy (not a eb creation), both methods work. Any ideas?

@tony-gutierrez

This comment has been minimized.

Copy link
Owner Author

@tony-gutierrez tony-gutierrez commented Jul 26, 2016

@williamdias, I usually launch instances with the sample app, and then upload my code, so it may not work on creation. It sounds like a timing issue. Let me know what you find out? Also, I don't get notified of comments here, so you might want to comment on the original blog post. Thanks!

@DkGr

This comment has been minimized.

Copy link

@DkGr DkGr commented Feb 17, 2017

Your script doesn't work anymore because nginx service is now always forced to run in all new AWS EBS instance.
So I modified one line to fix this in the 20_getcert command :

--pre-hook \"service nginx stop\"       become =>      --pre-hook \"initctl stop nginx\" --post-hook \"initctl start nginx\"

And I added a new command :

50_restartnginx:
  command: "sudo initctl restart nginx"

according to this stackoverflow fix

@Flo-D

This comment has been minimized.

Copy link

@Flo-D Flo-D commented Apr 1, 2017

Hi @tony-gutierrez, I really want to thank you for this code ! It seems to be working for the certificate (I'm still with LetsEncrypt staging server)

However I keep getting this error (from /var/log/nginx/error.log) with a 502 Bad Gateway nginx/1.10.1 :

upstream prematurely closed connection while reading response header from upstream, client: 76.67.169.189, server: localhost, request: "GET /favicon.ico HTTP/1.1", upstream: "http://127.0.0.1:8081/favicon.ico", host: "xxx-env.eu-central-1.elasticbeanstalk.com", referrer: "https://xxx-env.eu-central-1.elasticbeanstalk.com/"

I'm running a NodeJS server. I've been looking for a solution for hours and I can't figure out what's causing it...
I tried to set proxy_buffers 8 32k, proxy_buffer_size 64k, proxy_read_timeout 90;

Have you - or anybody else - an idea of what could be the problem ?

EDIT : Solved the issue, seems like the proxy headers are case sensitive, such as this line :

proxy_set_header        Connection      "upgrade";

should become :

proxy_set_header        Connection      "Upgrade";

See : https://github.com/websocket-rails/websocket-rails/issues/55

@timnovis

This comment has been minimized.

Copy link

@timnovis timnovis commented May 22, 2017

Thanks for this, absolute lifesaver! Do you have any tips on how I could automate renewal of the certificate?

@adailey14

This comment has been minimized.

Copy link

@adailey14 adailey14 commented Aug 15, 2017

This is a great gist, thanks for sharing it.

I'm exploring using certbot via the nginx plugin (certbot-auto --nginx) in a post deploy hook. Have you tried that at all?

@adailey14

This comment has been minimized.

Copy link

@adailey14 adailey14 commented Aug 15, 2017

FYI I did it via certbot-auto --nginx and was able to get it to work, with relatively simpler configuration. Hoping to make a script that can update ssls for new domains without a new deploy (for whitelabel client setup) by running certbot-auto -- nginx.

@jamesone

This comment has been minimized.

Copy link

@jamesone jamesone commented Aug 16, 2017

@adailey14 let us know how you go :)

@jamesone

This comment has been minimized.

Copy link

@jamesone jamesone commented Aug 16, 2017

Just out of curiosity, have you guys tried using AWS cert manager? They provide certs to you for free: https://aws.amazon.com/certificate-manager/pricing/

@starovoitovs

This comment has been minimized.

Copy link

@starovoitovs starovoitovs commented Sep 1, 2017

You are great, thank you!

@erm213

This comment has been minimized.

Copy link

@erm213 erm213 commented Sep 20, 2017

Does this handle the cert expiration? I know they are only valid for 90 days. My environment will probably be reloaded in that time, but just in case.

@utdrmac

This comment has been minimized.

Copy link

@utdrmac utdrmac commented Dec 20, 2017

@adailey14 can you share your simplified version?

@cichondev

This comment has been minimized.

Copy link

@cichondev cichondev commented Jan 9, 2018

Hello, I used this Gist to make mine, in which I use Apache. Who has interest, follow link: link gist .
Thanks @tony-gutierreztony-gutierrez, this was very helpful.

@SammyHam

This comment has been minimized.

Copy link

@SammyHam SammyHam commented Jan 24, 2018

There are some problems with Certbot for the mode standalone on ngnix i think. So I created another script based on yours for the Webroot mode and it works well.

https://github.com/SammyHam/LetsEncrypt-SSL-config-for-Elastic-Beanstalk

Thank you very much for sharing.

@devaffair

This comment has been minimized.

Copy link

@devaffair devaffair commented Feb 5, 2018

Hey
I got a noob's question..
What should be the value of the CERTDOMAIN attribute?

@jiawenzhang

This comment has been minimized.

Copy link

@jiawenzhang jiawenzhang commented Feb 24, 2018

I own a domain xxx.com
there is CNAME record:
parse.xxx.com. | CNAME | app-production.us-east-1.elasticbeanstalk.com

what should I use for the CERTDOMAIN?
xxx.com, or parse.xxx.com or app-production.us-east-1.elasticbeanstalk.com?

I have tried all them and I keep getting the following errors:

Domain: app-production.us-east-1.elasticbeanstalk.com
Type: connection
Detail: Fetching
https://app-production.us-east-1.elasticbeanstalk.com/.well-known/acme-challenge/2Jxww5TDK7BuWhRL_LDcaYJ3J6Wj53Qd117U9itqaKg:
Connection refused

To fix these errors, please make sure that your domain name was
entered correctly and the DNS A/AAAA record(s) for that domain
contain(s) the right IP address.

@slenky

This comment has been minimized.

Copy link

@slenky slenky commented Mar 5, 2018

@jiawenzhang , you would need to use domain name which has access from internet on 80 port. It's a simple LetsEncrypt verification step

@nabilfreeman

This comment has been minimized.

Copy link

@nabilfreeman nabilfreeman commented Mar 7, 2018

@jiawenzhang - make sure your certdomain variable is lowercase (unless you edited the script to be uppercase).

@godsmustbcrazy

This comment has been minimized.

Copy link

@godsmustbcrazy godsmustbcrazy commented Apr 26, 2018

@adailey14 Would you mind sharing your solution ?

@HoukasaurusRex

This comment has been minimized.

Copy link

@HoukasaurusRex HoukasaurusRex commented Nov 29, 2018

Anyone else getting an unauthorized error response from certbot?

image

Not sure if this is a problem with my domain (route-53) or with the .ebextensions config file

@dgnovo

This comment has been minimized.

Copy link

@dgnovo dgnovo commented Mar 21, 2019

@jiawenzhang

I own a domain xxx.com
there is CNAME record:
parse.xxx.com. | CNAME | app-production.us-east-1.elasticbeanstalk.com

what should I use for the CERTDOMAIN?
xxx.com, or parse.xxx.com or app-production.us-east-1.elasticbeanstalk.com?

I have tried all them and I keep getting the following errors:

Domain: app-production.us-east-1.elasticbeanstalk.com
Type: connection
Detail: Fetching
https://app-production.us-east-1.elasticbeanstalk.com/.well-known/acme-challenge/2Jxww5TDK7BuWhRL_LDcaYJ3J6Wj53Qd117U9itqaKg:
Connection refused

To fix these errors, please make sure that your domain name was
entered correctly and the DNS A/AAAA record(s) for that domain
contain(s) the right IP address.

I have the same problem, do you resolve it?
Can you explain to me how?
Thanks in advance

@dgnovo

This comment has been minimized.

Copy link

@dgnovo dgnovo commented Mar 21, 2019

Now i resolved problem with CNAME, but i have a problem with port / configuragion.
On my ELB, i have two listeners:
HTTP 80 => HTTP 80
and
HTTP 443 => HTTPS 443

Where can i corret?
Thanks

@tbezemer

This comment has been minimized.

Copy link

@tbezemer tbezemer commented May 9, 2019

My two cents:
I integrated @DkGr 's comment, but I ran into some problems because the command he/she used to restart nginx didn't run on my flavour of AWS Linux, so I replaced the start/stop/restart service commands with sudo service nginx ... (as described in the Gist, and as empirically tested to work for me on my system):

container_commands:
  10_installcertbot:
    command: "wget https://dl.eff.org/certbot-auto;chmod a+x certbot-auto"
  20_getcert:
    command: "sudo ./certbot-auto certonly --debug --non-interactive --email XXX@XXXX.XXX --agree-tos --standalone --domains ${certdomain} --keep-until-expiring --pre-hook \"sudo service nginx stop\" --post-hook \"sudo service nginx start\""
  30_link:
    command: "ln -sf /etc/letsencrypt/live/${certdomain} /etc/letsencrypt/live/ebcert"
  40_config:
    command: "mv /etc/nginx/conf.d/https_custom.pre /etc/nginx/conf.d/https_custom.conf"
  50_restartnginx:
    command: "sudo service nginx restart"
@fkeichfe

This comment has been minimized.

Copy link

@fkeichfe fkeichfe commented Jul 5, 2019

I followed the steps as described above and concerning the advice of @tbezemer.
The certificate is downloaded and installed, all steps from container_commands have been executed successfully, but the nginx does not use my custom configuration - in fact, the nginx configuration seems to be resetted to a standard configuration.
I checked this by connecting via SSH to the beanstalk-server. During deployment of my application I can see there for a short time my custom config file https_custom.conf in the folder /etc/nginx/conf.d, but a short time later it is gone...

Finally I discovered the solution for my problem - I had to modify the line 40_config as follows (50_restartnginx also seems not to be necessary, I could remove it without problem):

40_config:
    command: "mv /etc/nginx/conf.d/https_custom.pre /var/elasticbeanstalk/staging/nginx/conf.d/https_custom.conf"
@krissrex

This comment has been minimized.

Copy link

@krissrex krissrex commented Oct 7, 2019

This is what I ended up with:

# Dont forget to set the env variable "CERT_DOMAIN", and either fill in your email below or use an env variable for that too.
# Also note that this config is using the LetsEncrypt staging server, remove the flag when ready!

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:

  # The Nginx config forces https, and is meant as an example only. 
  /etc/nginx/conf.d/000_http_redirect_custom.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      server {
        listen 8080;
        server_name localhost;

        location /.well-known {
          # Certbot root
          alias /var/www/html/.well-known;
        }

        location / {
          return 301 https://$host$request_uri;
        }
      }
  
  # The Nginx config forces https, and is meant as an example only. 
  /etc/nginx/conf.d/https_custom.pre:
    mode: "000644"
    owner: root
    group: root
    content: |
      upstream nodejs {
          server 127.0.0.1:8081;
          keepalive 256;
      }
      # HTTPS server
      server {
        listen       443 default ssl;
        server_name  localhost;
        error_page  497 https://$host$request_uri;
        
        if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
          set $year $1;
          set $month $2;
          set $day $3;
          set $hour $4;
        }
        access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
        access_log  /var/log/nginx/access.log  main;
        
        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;
        if ($ssl_protocol = "") {
          rewrite ^ https://$host$request_uri? permanent;
        }
        
        gzip on;
        gzip_comp_level 4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        location / {
            proxy_pass  http://nodejs;
            proxy_set_header   Connection "";
            proxy_http_version 1.1;
            proxy_set_header        Host            $host;
            proxy_set_header        X-Real-IP       $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        Upgrade         $http_upgrade;
            proxy_set_header        Connection      "Upgrade";
        }
      }

  # Removes the default configuration and restarts the proxy server.
  /opt/elasticbeanstalk/hooks/configdeploy/post/99_kill_default_nginx.sh:
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash -xe
      rm -f /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf
      service nginx stop 
      service nginx start

packages: 
  yum:
    epel-release: [] 

container_commands:
  10_installcertbot:
    command: "wget https://dl.eff.org/certbot-auto;chmod a+x certbot-auto"
  20_getcert:
    command: "sudo ./certbot-auto certonly --webroot --webroot-path /var/www/html --debug --non-interactive --email xxx@xxx.com --agree-tos --domains ${CERT_DOMAIN} --keep-until-expiring"
  30_link:
    command: "ln -sf /etc/letsencrypt/live/${CERT_DOMAIN} /etc/letsencrypt/live/ebcert"
  40_config:
    command: "mv /etc/nginx/conf.d/https_custom.pre /etc/nginx/conf.d/https_custom.conf"
  50_removeconfig:
    command: "rm -f /tmp/deployment/config/#etc#nginx#conf.d#00_elastic_beanstalk_proxy.conf /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf"
@jakupi1

This comment has been minimized.

Copy link

@jakupi1 jakupi1 commented Dec 23, 2019

Any example ? i need to create .ebextensions folder and then inside it -> .config ?

@williamweckl

This comment has been minimized.

Copy link

@williamweckl williamweckl commented Dec 28, 2019

For a while my config was pretty much the same as @krissrex example, but I had some issues:

  • When I change my instance type or for some reason I had to terminate my ec2 instance to run another the first time the app initialized was an error setting up the certificate because nodejs and nginx wasn't ready yet
  • I have a server that responds to multiple domains, for example: www.mydomain.com and mydomain.com and @krissrex example does not support it

I modified my config to this one and it fits the needs I described above:

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 8080;
        server_name localhost;

        location /.well-known {
          # Certbot root
          alias /var/www/html/.well-known;
        }

        location / {
          return 301 https://$host$request_uri;
        }
      }

  /etc/nginx/conf.d/https_custom.pre:
    mode: "000644"
    owner: root
    group: root
    content: |
      upstream nodejs {
          server 127.0.0.1:8081;
          keepalive 256;
      }
      # HTTPS server
      server {
        listen       443 ssl http2 default;
        server_name  localhost;
        error_page  497 https://$host$request_uri;

        if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
          set $year $1;
          set $month $2;
          set $day $3;
          set $hour $4;
        }
        access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
        access_log  /var/log/nginx/access.log  main;

        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;
        if ($ssl_protocol = "") {
          rewrite ^ https://$host$request_uri? permanent;
        }

        gzip on;
        gzip_comp_level 4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        location / {
            proxy_pass  http://nodejs;
            proxy_set_header   Connection "";
            proxy_http_version 1.1;
            proxy_set_header        Host            $host;
            proxy_set_header        X-Real-IP       $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header        Upgrade         $http_upgrade;
            proxy_set_header        Connection      "Upgrade";
        }
      }

  # Cron to renew cert
  "/etc/cron.d/certbot_renew":
    mode: "000644"
    owner: root
    group: root
    content: |
      @weekly root /usr/local/bin/certbot-auto renew

  "/opt/elasticbeanstalk/hooks/appdeploy/post/100_install_cert_bot.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      echo "Instaling CERTBOT"

      wget https://dl.eff.org/certbot-auto
      mv certbot-auto /usr/local/bin/certbot-auto
      chown root /usr/local/bin/certbot-auto
      chmod 0755 /usr/local/bin/certbot-auto

      echo "CERTBOT installed."

  "/opt/elasticbeanstalk/hooks/appdeploy/post/101_configure_cert.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      source /etc/environment
      echo "Configuring CERTBOT for domains: ${CERT_DOMAINS}";

      certbot_command="/usr/local/bin/certbot-auto certonly --webroot --webroot-path /var/www/html --debug --non-interactive --email aws@duality.gg --agree-tos --keep-until-expiring"
      for domain in $(echo $CERT_DOMAINS | sed "s/,/ /g")
      do
        echo "Creating command for domain: $domain"
        certbot_command="$certbot_command -d $domain"
      done
      echo "Command to be runned: $certbot_command"

      echo "Restarting nginx"
      /etc/init.d/nginx restart

      echo "Running CERTBOT"
      eval $certbot_command
      echo "CERTBOT configured.";

  "/opt/elasticbeanstalk/hooks/appdeploy/post/102_link_cert.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      source /etc/environment
      domain="$( cut -d ',' -f 1 <<< "$CERT_DOMAINS" )";
      if [ -d /etc/letsencrypt/live ]; then
        echo "letsencrypt/live exists";
        domain_folder_name="$(ls /etc/letsencrypt/live | sort -n | grep $domain | head -1)";
        echo "domain_folder_name is $domain_folder_name";
        if [ -d /etc/letsencrypt/live/${domain_folder_name} ]; then
          echo "Creating sym link";
          ln -sf /etc/letsencrypt/live/${domain_folder_name} /etc/letsencrypt/live/ebcert
        fi
      fi

  "/opt/elasticbeanstalk/hooks/appdeploy/post/103_https_config.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      rm -f /etc/nginx/conf.d/https_custom.conf
      mv /etc/nginx/conf.d/https_custom.pre /etc/nginx/conf.d/https_custom.conf

  "/opt/elasticbeanstalk/hooks/appdeploy/post/104_remove_old_configs.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      if [ -f /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf ]; then
        echo "Moving /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf to .bak";
        rm -rf /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf.bak
        mv /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf.bak
      fi

  "/opt/elasticbeanstalk/hooks/appdeploy/post/105_restart_nginx.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      /etc/init.d/nginx restart

packages:
  yum:
    epel-release: []

container_commands:
  10_export_cert_domains_env:
    command: |
      grep -qxF CERT_DOMAINS="${CERT_DOMAINS}" /etc/environment || echo CERT_DOMAINS="${CERT_DOMAINS}" >> /etc/environment

I had to move all container_commands to post deploy scripts to make sure both nginx and nodejs are setted up before trying to setup the certificate.
I also had to setup the CERT_DOMAINS env variable to /etc/environments to be able to load it at post deploy script because post deploy scripts does not share the same app environment variables. To do that I'm using container_commands, all the rest was moved to post deploy scripts.

It also enables HTTP2 protocol.

To make it work you have to set an environment variable at elastic beanstalk config called CERT_DOMAINS. You can set it to multiple domains if you like, or to a single one if your use case is more simpler than mine.

Example of valid values for CERT_DOMAINS env:

  • domain.com,www.mydomain.com
  • www.mydomain.com,mydomain.com,app.mydomain.com
  • mydomain.com

@jakupi1 You can create a file with a name of your choice inside your app source code, inside path .ebextensions. For example, my file is called: .ebextensions/https-redirect.config. It's important that the extension of the file is .config. The content of the file can be exactly the same as my example above, it is already production ready.

@jakupi1

This comment has been minimized.

Copy link

@jakupi1 jakupi1 commented Jan 4, 2020

@williamweckl im getting strange errors, im using amazon aws linux ami

@williamweckl

This comment has been minimized.

Copy link

@williamweckl williamweckl commented Jan 5, 2020

@jakupi1 I'm using aws linux too. What errors are you getting? I had a lot of pain to make it work for the first time too 😢

@mjgall

This comment has been minimized.

Copy link

@mjgall mjgall commented Jan 19, 2020

@williamweckl I'm getting the following error when attempting to use your file - any insight?

[2020-01-19T21:06:38.844Z]` INFO [13932] - [Application update app-eddf-200119_150438@192/AppDeployStage1/AppDeployPostHook/105_restart_nginx.sh] : Activity execution failed, because: nginx: [emerg] BIO_new_file("/etc/letsencrypt/live/ebcert/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/letsencrypt/live/ebcert/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) nginx: configuration file /etc/nginx/nginx.conf test failed (ElasticBeanstalk::ExternalInvocationError) caused by: nginx: [emerg] BIO_new_file("/etc/letsencrypt/live/ebcert/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/letsencrypt/live/ebcert/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) nginx: configuration file /etc/nginx/nginx.conf test failed (Executor::NonZeroExitStatus)

@lucasdavila

This comment has been minimized.

Copy link

@lucasdavila lucasdavila commented Jan 26, 2020

@mjgall nginx is not finding the certificate at this location /etc/letsencrypt/live/ebcert/fullchain.pem so either it is not being generated, or it was generated in another location.

@tony-gutierrez

This comment has been minimized.

Copy link
Owner Author

@tony-gutierrez tony-gutierrez commented Jan 26, 2020

Ssh in and find it. You can also edit the nginx config and restart until they are correct. Then copy the changes to your files.

@encausticcello

This comment has been minimized.

Copy link

@encausticcello encausticcello commented Jan 29, 2020

Hi, I'm able to install certbot, stop nginx, get a certificate in standalone mode, but when I try to restart nginx I get the following error:

[emerg] host not found in upstream "nodejs" in /etc/nginx/conf.d/https_custom.conf:21

Any suggestions?

@ASchwad

This comment has been minimized.

Copy link

@ASchwad ASchwad commented Feb 17, 2020

@mjgall Have you found the problem for this error message?

I get the same error and found out, that no certificate is generated. The HTTP Challenges are failing although i specified the desired domains in route53 and configured them for the elb instance. I also opened the HTTP port in case it is needed for the http challenge. But unfortunately without any effect. The certification process still fails with "Connection refused". Any suggestions?

@ASchwad

This comment has been minimized.

Copy link

@ASchwad ASchwad commented Feb 17, 2020

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

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

I added two more security rules to the beginning of the script of @williamweckl and rebuild the environment. That did the job for my problem. Thank you guys!

@Archinowsk

This comment has been minimized.

Copy link

@Archinowsk Archinowsk commented Feb 23, 2020

Here is my solution which is combining from @tony-gutierrez, @krissrex, and @williamweckl: https://github.com/Archinowsk/konsti-server/tree/master/.ebextensions. Thanks!

  • Use container_commands instead of appdeploy/post/ hooks
  • Accept multiple domains
  • Use commands to clean up prior hooks and nginx configs on deploy
  • Split to multiple files
  • Cronjob for automatic certificate renewal
  • configdeploy/post/ hook to prevent recreation of default configs
  • www is redirected to non-www

// Edit: Some updates. Turns out cert challenge check was not working properly. Hopefully fixed now.

@frostbytedata

This comment has been minimized.

Copy link

@frostbytedata frostbytedata commented Feb 24, 2020

Here is my solution which is combining from @tony-gutierrez, @krissrex, and @williamweckl: https://github.com/Archinowsk/konsti-server/tree/master/.ebextensions. Thanks!

  • Use container_commands instead of appdeploy/post/ hooks
  • Accept multiple domains
  • Use commands to clean up prior hooks and nginx configs on deploy
  • Split to multiple files
  • Cronjob for automatic certificate renewal
  • configdeploy/post/ hook to prevent recreation of default configs
  • www is redirected to non-www

@Archinowsk This is awesome work. Thanks so much for taking the time to share this. One question I have, why is the 03_nginx.config after the certificate generation? Wouldn't this need to be run and configured prior to the certbot verification process?

@Archinowsk

This comment has been minimized.

Copy link

@Archinowsk Archinowsk commented Feb 25, 2020

@Archinowsk This is awesome work. Thanks so much for taking the time to share this. One question I have, why is the 03_nginx.config after the certificate generation? Wouldn't this need to be run and configured prior to the certbot verification process?

Good question :D. My thought was that nginx config is referring to the certificate files -> create those first. Not completely sure if the order matters at that point.

@frostbytedata

This comment has been minimized.

Copy link

@frostbytedata frostbytedata commented Feb 25, 2020

From the EB Docs: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html#linux-container-commands

You can use the container_commands key to execute commands that affect your application source code. Container commands run after the application and web server have been set up and the application version archive has been extracted, but before the application version is deployed. Non-container commands and other customization operations are performed prior to the application source code being extracted.

So looks like the order of the files does not matter, it is the type of operation the file describes that determines when it will execute in the EB deployment lifecycle. In short, your code should work fine. I am however still struggling to get this to work. Off to do more tinkering...

@hjaimee

This comment has been minimized.

Copy link

@hjaimee hjaimee commented Mar 12, 2020

Did you ever manage to get @Archinowsk configs to work?

Still getting the following error No such file or directory:fopen('/etc/letsencrypt/live/ebcert/fullchain.pem','r')

@frostbytedata

This comment has been minimized.

Copy link

@frostbytedata frostbytedata commented Mar 12, 2020

I did end up getting it to work. It is worth noting I do have this config split into separate files, just adding them all at once here for simplicity. If you try and use this all in one file you may have issues since there are multiple files: declarations in one script. Here is my concatenated config as one piece:

commands:
  10_cleanup:
    command: |
      rm -f /opt/elasticbeanstalk/hooks/configdeploy/post/*
      rm -f /etc/nginx/conf.d/*

container_commands:
  20_remove_default_configs:
    command: |
      rm -f /tmp/deployment/config/#etc#nginx#conf.d#00_elastic_beanstalk_proxy.conf
      rm -f /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf
container_commands:
  10_install_certbot:
    command: |
      wget https://dl.eff.org/certbot-auto
      mv certbot-auto /usr/local/bin/certbot-auto
      chown root /usr/local/bin/certbot-auto
      chmod 0755 /usr/local/bin/certbot-auto
  20_configure_cert:
    command: |
      certbot_command="/usr/local/bin/certbot-auto certonly --webroot --webroot-path /var/www/html --debug --non-interactive --email ${CERT_EMAIL} --agree-tos --keep-until-expiring --expand"
      for domain in $(echo ${CERT_DOMAINS} | sed "s/,/ /g")
      do
        certbot_command="$certbot_command -d $domain"
      done
      eval $certbot_command
  30_link_cert:
    command: |
      domain="$( cut -d ',' -f 1 <<< "${CERT_DOMAINS}" )";
      if [ -d /etc/letsencrypt/live ]; then
        domain_folder_name="$(ls /etc/letsencrypt/live | sort -n | grep $domain | head -1)";
        if [ -d /etc/letsencrypt/live/${domain_folder_name} ]; then
          ln -sfn /etc/letsencrypt/live/${domain_folder_name} /etc/letsencrypt/live/ebcert
        fi
      fi
files:
  "/etc/nginx/conf.d/000_http_redirect_custom.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      server {
        listen 8080;
        server_name localhost;

        location /.well-known {
          # Certbot root
          alias /var/www/html/.well-known;
        }
        location / {
          return 301 https://$host$request_uri;
        }
      }
  "/etc/nginx/conf.d/https_custom.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      upstream nodejs {
          server 127.0.0.1:8081;
          keepalive 256;
      }
      # HTTPS server
      server {
        listen 443 ssl default;
        server_name localhost;
        error_page 497 https://$host$request_uri;
        if ($host ~* ^www\.(.*)) {
          set $host_without_www $1;
          rewrite ^(.*) https://$host_without_www$1 permanent;
        }
        if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
          set $year $1;
          set $month $2;
          set $day $3;
          set $hour $4;
        }
        access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
        access_log /var/log/nginx/access.log main;
        location / {
            proxy_pass http://nodejs;
            proxy_set_header Connection "";
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
        }
        gzip on;
        gzip_comp_level 4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
        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;
        if ($ssl_protocol = "") {
          rewrite ^ https://$host$request_uri? permanent;
        }
      }
files:
  # Elastic Beanstalk recreates the default configuration during every configuration deployment
  "/opt/elasticbeanstalk/hooks/configdeploy/post/99_kill_default_nginx.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash -xe
      rm -f /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf
      service nginx stop
      service nginx start
files:
  # Cron to renew cert
  "/etc/cron.d/certbot_renew":
    mode: "000644"
    owner: root
    group: root
    content: |
      @weekly root /usr/local/bin/certbot-auto renew
@hjaimee

This comment has been minimized.

Copy link

@hjaimee hjaimee commented Mar 15, 2020

Hey,

This is still not working for me. When I run this then connection is refused on both HTTP and HTTPS. I've added these connections to the security group of the AWS beanstalk.

@Archinowsk

This comment has been minimized.

Copy link

@Archinowsk Archinowsk commented Mar 23, 2020

For some reason my /etc/nginx/nginx.conf file didn't have include /etc/nginx/conf.d/*.conf; anymore so my configs were not loaded. I ended up deleting the default file and creating it myself.

Full example: https://github.com/Archinowsk/konsti-server/tree/9de485e25ac2cf757d3e4b0b73ef662bda766847/.ebextensions

files:
  "/etc/nginx/nginx.pre":
    mode: "000644"
    owner: root
    group: root
    content: |
      user nginx;
      worker_processes auto;
      error_log /var/log/nginx/error.log;
      pid /var/run/nginx.pid;

      events {
        worker_connections  1024;
      }

      http {
        port_in_redirect off;
        default_type application/octet-stream;

        log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';

        log_format healthd '$msec"$uri"$status"$request_time"$upstream_response_time"$http_x_forwarded_for';

        access_log /var/log/nginx/access.log  main;
        sendfile on;
        keepalive_timeout 65;

        include /etc/nginx/mime.types;
        include /etc/nginx/conf.d/*.conf;
      }

container_commands:
  10_setup_nginx:
    command: |
      sudo rm -f /tmp/deployment/config/#etc#nginx#nginx.conf
      sudo rm -f /etc/nginx/nginx.conf

      sudo mv /etc/nginx/nginx.pre /etc/nginx/nginx.conf

      sudo service nginx stop
      sudo service nginx start
@seankarson

This comment has been minimized.

Copy link

@seankarson seankarson commented Mar 28, 2020

@Archinowsk Thanks so much! Worked like a charm!

@theIYD

This comment has been minimized.

Copy link

@theIYD theIYD commented Apr 6, 2020

@mjgall nginx is not finding the certificate at this location /etc/letsencrypt/live/ebcert/fullchain.pem so either it is not being generated, or it was generated in another location.

Any solution to this ? Facing the same problem

@russau

This comment has been minimized.

Copy link

@russau russau commented Apr 12, 2020

Here is something I have working for a python flask Apache beanstalk application: https://github.com/russau/renew-certs-flask

The project from @Archinowsk was a big help getting this done.

@cavasinf

This comment has been minimized.

Copy link

@cavasinf cavasinf commented Apr 14, 2020

Here is mine:

  • Symfony project
  • HTTPD (Apache)
  • In AWS working with ElasticBeanstalk env (using the function GetOptionSetting)
  • With multiple domains (separated by ,)
# Dont forget to set the env variable "HTTPS_CERT_DOMAIN", and either fill in your email below or use an env variable for that too.
# @source https://gist.github.com/cichondev/08056094a27cfcc7424afc3afcbf781c

packages:
  yum:
    epel-release: []
    mod24_ssl : []

# ElasticBeanstalk 'env' settings
# WARNING : This part is only the .dist setting,
# if you want to update data, change it here (if needed) BUT ALSO in the ElasticBeanstalk console (EB -> Configuration -> Logiciels -> Propriétés de l'environnement)
option_settings:
  aws:elasticbeanstalk:application:environment:
        HTTPS_CERT_DOMAIN: your-domain.com,your-domain.eu-west-3.elasticbeanstalk.com'
        HTTPS_CONTACT_EMAIL: 'admin@email.com'

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

files:
  # The Apache config forces http to https, REPLACE all {{HTTPS_CERT_DOMAIN}} by ONE domain name.
  /etc/httpd/conf.d/http_redirect_template.pre:
    mode: "000644"
    owner: root
    group: root
    content: |
      <VirtualHost *:80>
        ServerName {{HTTPS_CERT_DOMAIN}}
        ServerAlias www.{{HTTPS_CERT_DOMAIN}}
        DocumentRoot "/var/www/html/public"
        RewriteEngine on
        RewriteCond %{SERVER_NAME} ={{HTTPS_CERT_DOMAIN}} [OR]
        RewriteCond %{SERVER_NAME} =www.{{HTTPS_CERT_DOMAIN}}
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
      </VirtualHost>

  # The Apache config https, REPLACE all {{HTTPS_CERT_DOMAIN}} by ONE domain name.
  /etc/httpd/conf.d/https_custom_template.pre:
    mode: "000644"
    owner: root
    group: root
    content: |
      # HTTPS server
      <IfModule mod_ssl.c>
      <VirtualHost *:443>
        ServerName {{HTTPS_CERT_DOMAIN}}
        ServerAlias www.{{HTTPS_CERT_DOMAIN}}
        DocumentRoot "/var/www/html/public"
        RewriteEngine on
        SSLCertificateFile /etc/letsencrypt/live/{{HTTPS_CERT_DOMAIN}}/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/{{HTTPS_CERT_DOMAIN}}/privkey.pem
        Include /etc/letsencrypt/options-ssl-apache.conf
      </VirtualHost>
      </IfModule>

  /tmp/letsencrypt/options-ssl-apache.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      SSLEngine on
      # Intermediate configuration, tweak to your needs
      SSLProtocol             all -SSLv2 -SSLv3
      SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
      SSLHonorCipherOrder     on
      SSLOptions +StrictRequire
      # Add vhost name to log entries:
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
      LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
      #CustomLog /var/log/apache2/access.log vhost_combined
      #LogLevel warn
      #ErrorLog /var/log/apache2/error.log
      # Always ensure Cookies have "Secure" set (JAH 2012/1)
      #Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"

  # Certbot (HTTPS) install
  "/opt/elasticbeanstalk/hooks/appdeploy/post/100_install_cert_bot.sh":
      mode: "000755"
      owner: root
      group: root
      content: |
        #!/usr/bin/env bash
        echo "Installing CERTBOT.."

        wget https://dl.eff.org/certbot-auto
        mv certbot-auto /usr/local/bin/certbot-auto
        chown root /usr/local/bin/certbot-auto
        chmod 0755 /usr/local/bin/certbot-auto

        echo "CERTBOT installed!"

  # Certbot (HTTPS) config cert
  "/opt/elasticbeanstalk/hooks/appdeploy/post/101_configure_cert.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash

      domains="`{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "HTTPS_CERT_DOMAIN"}}`"
      https_contact_email="`{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "HTTPS_CONTACT_EMAIL"}}`"

      echo "Configuring CERTBOT for domains: $domains";

      stop httpd || true

      certbot_command="/usr/local/bin/certbot-auto certonly --debug --non-interactive --email $https_contact_email --agree-tos --standalone"

      echo "Removing OLD 000_http* (.config) files"
      rm -Rf /etc/httpd/conf.d/000_http*

      for domain in $(echo $domains | sed "s/,/ /g")
      do
        echo "=== $domain ==="
        echo "**  Setting up CERTBOT for $domain"
        certbot_command_domain="$certbot_command --domains $domain"

        echo "** Running CERTBOT for $domain.."
        eval $certbot_command_domain
        echo "CERTBOT configured!";

        echo "**  Creating HTTPS VirtualHost:443 for $domain.."
        formatted_domain=$(echo $domain | sed "s/\./_/g")
        sed -e "s/{{HTTPS_CERT_DOMAIN}}/$domain/g" /etc/httpd/conf.d/https_custom_template.pre > /etc/httpd/conf.d/000_https_custom_$formatted_domain.conf
        echo "**  Creating redirect HTTP to HTTPS for $domain.."
        sed -e "s/{{HTTPS_CERT_DOMAIN}}/$domain/g" /etc/httpd/conf.d/http_redirect_template.pre > /etc/httpd/conf.d/000_http_redirect_$domain.conf
      done

      echo "Removing .config templates.."
      rm -Rf /etc/httpd/conf.d/https_custom_template.pre
      rm -Rf /etc/httpd/conf.d/http_redirect_template.pre

      mv /tmp/letsencrypt/options-ssl-apache.conf /etc/letsencrypt/options-ssl-apache.conf

      echo "Restarting HTTPD.."
      start httpd
      restart httpd


  # Cron to renew cert
  "/etc/cron.d/certbot_renew":
    mode: "000644"
    owner: root
    group: root
    content: |
      @weekly root /usr/local/bin/certbot-auto renew

  # Redo Jobs if server restarted from EB
  "/opt/elasticbeanstalk/hooks/restartappserver/post/101_redo_certbot.sh":
      mode: "000755"
      owner: root
      group: root
      content: |
        #!/usr/bin/env bash
        echo "Server has been restart from EB"
        /opt/elasticbeanstalk/hooks/appdeploy/post/101_configure_cert.sh
@russau

This comment has been minimized.

Copy link

@russau russau commented Apr 14, 2020

I've updated my project to work with the new Amazon Linux 2 Python 3.7 platform: https://github.com/russau/renew-certs-flask-nginx

There are new "platform hook" features to make this a bit easier: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/platforms-linux-extend.html

@corei8

This comment has been minimized.

Copy link

@corei8 corei8 commented Apr 19, 2020

Following the script of @cavasinf, I get the following error in /var/log/eb-activity.log:

StandaloneBindError: Problem binding to port 80: Could not bind to IPv4 or IPv6.
Please see the logfiles in /var/log/letsencrypt for more details.

Multiple "no such file or directory" errors follow, which I suppose are a result of the above error, which is the first. I am not sure if there is a problem with the file itself or this is unique to me.

@cavasinf

This comment has been minimized.

Copy link

@cavasinf cavasinf commented Apr 19, 2020

Following the script of @cavasinf, I get the following error in /var/log/eb-activity.log:

StandaloneBindError: Problem binding to port 80: Could not bind to IPv4 or IPv6.
Please see the logfiles in /var/log/letsencrypt for more details.

@corei8 In my case I want the HTTP to redirect to the HTTPS.
But in your case you may disable the port :80 and only keep the :443

Can you go into your EC2 of EB, and check the SecurityGroup -> Inbound rules ?
You need to have at least :

Type Protocole Plage de ports Source
HTTP TCP 80 0.0.0.0/0
HTTPS TCP 443 0.0.0.0/0

image

@HausCloud

This comment has been minimized.

Copy link

@HausCloud HausCloud commented May 10, 2020

My solution @ https://github.com/HausCloud/AWS-ElasticBeanstalk-SSL
Additional support requests/bug reports are welcome.

@zalbastaki

This comment has been minimized.

Copy link

@zalbastaki zalbastaki commented May 21, 2020

@mjgall nginx is not finding the certificate at this location /etc/letsencrypt/live/ebcert/fullchain.pem so either it is not being generated, or it was generated in another location.

Any solution to this ? Facing the same problem

@williamweckl I'm getting the following error when attempting to use your file - any insight?

[2020-01-19T21:06:38.844Z]` INFO [13932] - [Application update app-eddf-200119_150438@192/AppDeployStage1/AppDeployPostHook/105_restart_nginx.sh] : Activity execution failed, because: nginx: [emerg] BIO_new_file("/etc/letsencrypt/live/ebcert/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/letsencrypt/live/ebcert/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) nginx: configuration file /etc/nginx/nginx.conf test failed (ElasticBeanstalk::ExternalInvocationError) caused by: nginx: [emerg] BIO_new_file("/etc/letsencrypt/live/ebcert/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/letsencrypt/live/ebcert/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) nginx: configuration file /etc/nginx/nginx.conf test failed (Executor::NonZeroExitStatus)

Have you found a solution to this? Currently facing the same error.

@panagath

This comment has been minimized.

Copy link

@panagath panagath commented Jun 10, 2020

Hello all. I have been trying to set this up. Although it seems that certbot downloads a certificate all well and that the custom files are put inside the nginx config.d directory and that nginx restarts properly...I still can't seem to be able to reach the API using Https... Actually now even http is broken. Any ideas?

  • I have listen 80; as my EB is listening on 80
  • I also have proxy_pass http://docker; and have also tried proxy_pass http://127.0.0.1:5000; as I'm running a Java setup

Here is my current config:

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://127.0.0.1:5000;
          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 myemail@email.com --agree-tos --domains mydomain.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_ls_nginx_config:
    command: "ls -la /etc/nginx/conf.d"


EDIT:

I was polling the nginx config directory. The script seems to be working fine and puts the files in the nginx directory. But after this finishes, just before the app starts, something totally resets nginx, the directory is deleted and put back in place. So those files get deleted and the config is not there anymore. Any ideas?

@DavidSanek

This comment has been minimized.

Copy link

@DavidSanek DavidSanek commented Aug 23, 2020

The solution from @HausCloud works perfectly. Although I got permissions denied error, I managed to fix it by following this issue.

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 9, 2020

Hey @DavidSanek, I'm quite new to EB and the associated stuff. Would you be kind enough to post your final solution of the setup you have in .ebextensions (incorporating @HausCloud certbot stuff)? Regards

@DavidSanek

This comment has been minimized.

Copy link

@DavidSanek DavidSanek commented Sep 12, 2020

Hey @mstarcevic, I've created a Gist with both files that I needed to setup certbot.

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 12, 2020

@DavidSanek Thank you very much. Appreciated.

@HausCloud

This comment has been minimized.

Copy link

@HausCloud HausCloud commented Sep 12, 2020

@panagath Were you able to figure it out?

@sinmarcus3

This comment has been minimized.

Copy link

@sinmarcus3 sinmarcus3 commented Sep 17, 2020

Hey @HausCloud, I used your solution in your Gist and it deployed successfully with no errors. But https doesn't seem to work. The http version works fine though. Is there a step after deployment that I'm missing?

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 17, 2020

@sinmarcus3 I got it working just last week. Let me go back over my notes later today and I'll let you know what exactly I did.

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 17, 2020

@sinmarcus3 Details as promised:

  1. Make sure that you are running on Amazon Linux 2.
  2. Make sure you do the eb labs download.
  3. Put the .platform folder at the same level as the .elasticbeanstalk folder.
  4. In .platform/hooks/postdeploy/00_ssl_setup_certbost.sh script, set the following variables manually:
    CERTBOT_NAME
    CERTBOT_EMAIL
    CERTBOT_DOMAINS
    ***Note the original version of the script had these coming through as environment variables but the in the revised script, you set them manually.
  5. In the .ebextensions folder, have the two config files as follows (you can call them what you want):

single_instance.config
option_settings:
- namespace: aws:elasticbeanstalk:environment
option_name: EnvironmentType
value: SingleInstance

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

  1. Make sure that you deploy the .platform and .ebextensions folders, both at the top level of your zip.
  2. Run eb deploy
@sinmarcus3

This comment has been minimized.

Copy link

@sinmarcus3 sinmarcus3 commented Sep 17, 2020

Thanks @mstarcevic. It seemed to deploy fine with no errors or issues. The logs even suggest the certificate was successfully enabled:
Congratulations! You have successfully enabled...
The http site works fine but the https still doesn't respond and refuses to connect with the following error: ERR_CONNECTION_REFUSED

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 17, 2020

@sinmarcus3 I can't remember anything else that I may have had to do, either in eb or on the AWS console side of things. My domain names are registered through Route 53 and I know I did a bit of stuffing around with Alias records and such. But I'm pretty sure that I also got it going with the environment URL directly. How are you checking? Postman?

Did you include the eb environment URL in the domain names CERTBOT variable? And what format did you use, assuming you did...

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 17, 2020

I think that I included IP addresses somewhere along the line but no longer. However, you did remind me of one other thing... In the corresponding EC2 instance in the incoming rules, I have a provision for port 443. I think that I had to put that in... in any case, it's there...
ie port 443, TCP, 0.0.0.0/0 (for the security group tied in with the eb). That's to go with ports 80 and 22...

@HausCloud

This comment has been minimized.

Copy link

@HausCloud HausCloud commented Sep 17, 2020

@sinmarcus3 Can you curl localhost:80 and localhost:443? Check your config. You may have a load balancer attached.

@sinmarcus3

This comment has been minimized.

Copy link

@sinmarcus3 sinmarcus3 commented Sep 17, 2020

@HausCloud It failed with connection refused on port 443. Have checked the config and there's no load balancer is attached.

@jhaist

This comment has been minimized.

Copy link

@jhaist jhaist commented Sep 18, 2020

@sinmarcus3 I am having the exact same issue as you. I can see that the SSL certificate was successfully enabled, but I am also getting connection refused on port 443.

@sinmarcus3

This comment has been minimized.

Copy link

@sinmarcus3 sinmarcus3 commented Sep 18, 2020

@HausCloud I just stepped through it with a fresh sample app. The problem occurs when I input multiple domains in 00_ssl_setup_certbot.sh, i.e., CERTBOT_DOMAINS = "somedomain.ap-southeast-2.elasticbeanstalk.com,somedomain.co.nz,www.somedomain.co.nz". The code says the certificate is successfully installed and the http site will work, but the https version of the site will return a connection refused error.

@mstarcevic

This comment has been minimized.

Copy link

@mstarcevic mstarcevic commented Sep 18, 2020

@sinmarcus3 I'm inputting 2 domains and it still works.

@HausCloud

This comment has been minimized.

Copy link

@HausCloud HausCloud commented Sep 18, 2020

@sinmarcus3 Want to set up a zoom call? Let's see if we can debug it.

@jhaist

This comment has been minimized.

Copy link

@jhaist jhaist commented Sep 18, 2020

When I SSH into my EC2 console and run telnet <my_ip_address> 443, I am receiving connection refused. Any ideas?

@sinmarcus3

This comment has been minimized.

Copy link

@sinmarcus3 sinmarcus3 commented Sep 18, 2020

@HausCloud I managed to get it to work by rebuilding the environment. I didn't make any other changes.

@EffyCoder

This comment has been minimized.

Copy link

@EffyCoder EffyCoder commented Sep 23, 2020

@sinmarcus3 Want to set up a zoom call? Let's see if we can debug it.

HausCloud I tried setting up https using your code. But I found that nothing sort of happens even the logging things. I have set up domain for single instance in the said variable still it didn't work. I even tried it with fresh environment setup. But no luck.

@gavleavitt

This comment has been minimized.

Copy link

@gavleavitt gavleavitt commented Sep 28, 2020

@sinmarcus3, @HausCloud and @jhaist I am having a similar issue, when I deploy my application HTTPS doesn't work while HTTP does. However, if I rebuild the environment then HTTPS and HTTPS redirect start working until I deploy again, which requires another rebuild.

@optimistiks

This comment has been minimized.

Copy link

@optimistiks optimistiks commented Oct 4, 2020

same issue as @gavleavitt, when I use the solution from @HausCloud https stops working after CodePipeline deploy phase, rebuilding environment helps. Tried some things like removing the hook script after first run, but no luck. (I have single domain)

@HausCloud

This comment has been minimized.

Copy link

@HausCloud HausCloud commented Oct 5, 2020

@EffyCoder @gavleavitt Can you post your EB setup so I can try to reproduce the issue?
@optimistiks Double check your config is a actually a single instance. See if the permissions_fix helps. Check your connected EC2 instance if the security settings are listening on 443. Otherwise, try a few curls in and out of the instance to debug.

@gskoljarev

This comment has been minimized.

Copy link

@gskoljarev gskoljarev commented Oct 8, 2020

@HausCloud @optimistiks @gavleavitt @sinmarcus3. Had a similar issue, but I sorted it out - found that postdeploy script was filling /etc/nginx/nginx.conf with duplicate server_names_hash_bucket_size 192; entries, creating an invalid nginx configuration. Had to ssh into the instance (as root), run the postdeploy script manually and check the conf with nginx -t.

@gskoljarev

This comment has been minimized.

Copy link

@gskoljarev gskoljarev commented Oct 8, 2020

I've modified the postdeploy script as following (also commented out the 'Prevent certificate installation if not clean sample app' part).

...
HTTP_STRING='^http\s*{$'
NAME_LIMIT='http {\nserver_names_hash_bucket_size 192;\n'
SERVER_NAMES_HASH='nserver_names_hash_bucket_size 192;'

# Prevent replace if not clean sample app

if ! grep -Fxq "SERVER_NAMES_HASH" /etc/nginx/nginx.conf; then
    # Increase size of string name for --domains (for default EB configs)
    
    if ! sed -i "s/$HTTP_STRING/$NAME_LIMIT/g" /etc/nginx/nginx.conf; then
        log_and_exit 'ERROR: Changing server name limit failed'
    fi
fi

# # Prevent certificate installation if not clean sample app

# CERT_REGEX="Certificate Name:\s+$CERTBOT_NAME"

# if certbot certificates | grep -Ew "$CERT_REGEX"; then
#     log_and_exit 'INFO: Certificate already installed.'
# fi

# Set up certificates
...
@morsanu

This comment has been minimized.

Copy link

@morsanu morsanu commented Oct 16, 2020

I almost cried when I saw the lock icon as I spent 2 days running Linux commands. Just wanted to thank you for all the solutions in this thread.

@EffyCoder

This comment has been minimized.

Copy link

@EffyCoder EffyCoder commented Oct 23, 2020

I followed the steps as described above and concerning the advice of @tbezemer.
The certificate is downloaded and installed, all steps from container_commands have been executed successfully, but the nginx does not use my custom configuration - in fact, the nginx configuration seems to be resetted to a standard configuration.
I checked this by connecting via SSH to the beanstalk-server. During deployment of my application I can see there for a short time my custom config file https_custom.conf in the folder /etc/nginx/conf.d, but a short time later it is gone...

Finally I discovered the solution for my problem - I had to modify the line 40_config as follows (50_restartnginx also seems not to be necessary, I could remove it without problem):

40_config:
    command: "mv /etc/nginx/conf.d/https_custom.pre /var/elasticbeanstalk/staging/nginx/conf.d/https_custom.conf"

Your comment saved me. I was struggling for month. Trying out various configurations. But had no luck.

@vahiwe

This comment has been minimized.

Copy link

@vahiwe vahiwe commented Nov 14, 2020

I tried using the container_commands to setup the nginx configuration files but i couldn't find the files after successful deployment. After carrying out some research, I was pointed to this AWS documentation on how to override nginx configuration files. My folder structure is shown below.

~/workspace/my-app/
|-- .platform
|   -- nginx
|       -- nginx.conf
|       -- conf.d
|           -- https_custom.conf
|-- .ebextensions
|   -- ssl.config

I also had issues with installing the epel using the yum package manager on Amazon Linux 2. So i tweaked the setup to use rpm to install epel.

Here is the content of my ssl.config:

# Dont forget to set the env variable "DOMAIN_LINK" and either fill in your email below or set the env variable "EMAIL_LINK" for that too.

--- 
Resources: 
  sslSecurityGroupIngress: 
    Properties: 
      CidrIp: 0.0.0.0/0
      FromPort: 443
      GroupId: 
        ? "Fn::GetAtt"
        : 
          - AWSEBSecurityGroup
          - GroupId
      IpProtocol: tcp
      ToPort: 443
    Type: "AWS::EC2::SecurityGroupIngress"

files: 
  /etc/cron.d/certbot_renew: 
    content: "@weekly root certbot renew\n"
    group: root
    mode: "000644"
    owner: root
    
container_commands:
  10_downloadepel: 
    command: "sudo wget -r --no-parent -A 'epel-release-*.rpm' https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/"
  20_installepel: 
    command: "sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm --force"
  30_enableepl: 
    command: "sudo yum-config-manager --enable epel*"
  40_installcertbot: 
    command: "sudo yum install -y certbot"
  50_getcert: 
    command: "sudo certbot certonly --debug --non-interactive --email ${EMAIL_LINK} --agree-tos --standalone --domains ${DOMAIN_LINK} --keep-until-expiring --pre-hook \"sudo service nginx stop\" --post-hook \"sudo service nginx start\""
  60_link: 
    command: "ln -sf /etc/letsencrypt/live/${DOMAIN_LINK} /etc/letsencrypt/live/ebcert"

Below is the content of nginx.conf:

#Elastic Beanstalk Nginx Configuration File

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    32137;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';

    include       conf.d/*.conf;

    map $http_upgrade $connection_upgrade {
        default     "upgrade";
    }

    server {
        listen        80 default_server;
        access_log    /var/log/nginx/access.log main;

        client_header_timeout 60;
        client_body_timeout   60;
        keepalive_timeout     60;
        gzip                  off;
        gzip_comp_level       4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # Include the Elastic Beanstalk generated locations
        include conf.d/elasticbeanstalk/*.conf;
    }
}

Below is the content of https_custom.conf:

upstream nodejs {
    server 127.0.0.1:3030;
    keepalive 256;
}
# HTTPS server
server {
    listen       443 default ssl;
    server_name  localhost;
    error_page 497 https://$host$request_uri;

    if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
    set $year $1;
    set $month $2;
    set $day $3;
    set $hour $4;
    }

    access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
    
    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;
    if ($ssl_protocol = "") {
    rewrite ^ https://$host$request_uri? permanent;
    }
    location ~ ^/(lib/|img/) {
    root /var/app/current/public;
    access_log off;
    }
    location / {
        proxy_pass  http://nodejs;
        proxy_set_header   Connection "";
        proxy_http_version 1.1;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        Upgrade         $http_upgrade;
        proxy_set_header        Connection      "Upgrade";
    }
}

Hopefully this helps.

@aivoric

This comment has been minimized.

Copy link

@aivoric aivoric commented Nov 26, 2020

I am getting the following error:
"Your system is not supported by certbot-auto anymore.
Certbot cannot be installed.
"

It looks like certbot-auto was deprecated:
certbot/certbot@cac9d8f

Here is a discussion:
https://community.letsencrypt.org/t/your-system-is-not-supported-by-certbot-auto-anymore/135504/21

Any other ideas about how to get it to work?

Thank you.

@HausCloud

This comment has been minimized.

Copy link

@HausCloud HausCloud commented Nov 26, 2020

I almost cried when I saw the lock icon as I spent 2 days running Linux commands. Just wanted to thank you for all the solutions in this thread.

@morsanu Happy to help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.