Skip to content

Instantly share code, notes, and snippets.

@tony-gutierrez
Last active March 7, 2024 11:29
Show Gist options
  • Save tony-gutierrez/198988c34e020af0192bab543d35a62a to your computer and use it in GitHub Desktop.
Save tony-gutierrez/198988c34e020af0192bab543d35a62a to your computer and use it in GitHub Desktop.
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"
@HausCloud
Copy link

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

@jhaist
Copy link

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
Copy link

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
Copy link

@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
Copy link

@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
Copy link

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
Copy link

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
Copy link

@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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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.

@putuyoga
Copy link

putuyoga commented Dec 3, 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.

You can install the original certbot, instead of certbot-auto

      wget -O epel.rpm –nv https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
      sudo yum install -y ./epel.rpm
      sudo yum install -y python2-certbot-apache.noarch

then replace ./certbot-auto command with certbot

@andylolu2
Copy link

Thank you so much @vahiwe, it worked flawlessly.

@vahiwe
Copy link

vahiwe commented Jan 9, 2021

Thank you so much @vahiwe, it worked flawlessly.

@andylolu2 you're welcome. You can reference an article I wrote on it here.

@zedy
Copy link

zedy commented Jan 13, 2021

@vahiwe I took the OP's post and combined it with your solution from (above) changed a few params to work with Apache and got this:

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/httpd/conf.d/ssl.pre:
        mode: "000644"
        owner: root
        group: root
        content: |
            LoadModule ssl_module modules/mod_ssl.so
            Listen 443

            <VirtualHost *:443>
                <Directory /opt/python/current/app/build/static>
                    Order deny,allow
                    Allow from all
                </Directory>
                
                SSLEngine on
                SSLCertificateFile "/etc/letsencrypt/live/${MY_DOMAIN}/fullchain.pem"
                SSLCertificateKeyFile "/etc/letsencrypt/live/${MY_DOMAIN}/privkey.pem"
                SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
                SSLProtocol All -SSLv2 -SSLv3
                SSLHonorCipherOrder On
                SSLSessionTickets Off
                
                Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
                Header always set X-Frame-Options DENY
                Header always set X-Content-Type-Options nosniff
                
                ProxyPass / http://localhost:80/ retry=0
                ProxyPassReverse / http://localhost:80/
                ProxyPreserveHost on
                RequestHeader set X-Forwarded-Proto "https" early
                # If you have pages that may take awhile to
                # respond, add a ProxyTimeout:
                # ProxyTimeout seconds
            </VirtualHost>
  
    /tmp/renew_cert_cron:
        mode: "000777"
        owner: root
        group: root
        content: |
            # renew Lets encrypt cert with certbot command
            0 1,13 * * * /tmp/certbot-auto renew

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

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 test@test.com --agree-tos --standalone --domains ${MY_DOMAIN} --keep-until-expiring --pre-hook \"sudo service httpd stop\" --post-hook \"sudo service httpd start\""
    60_link:
        command: "ln -sf /etc/letsencrypt/live/${MY_DOMAIN} /etc/letsencrypt/live/ebcert"

And it executes (all commends run) but https is still not working (http works fine, but https immediately returns 'Unable to connect') Any ideas? I've been stuck for 2 days now.

@vahiwe
Copy link

vahiwe commented Jan 13, 2021

@zedy I haven’t used Apache in any of my projects so might not really know what works and what doesn’t. But we can try connecting and solving it together.

@zedy
Copy link

zedy commented Jan 14, 2021

@vahiwe Thanks for the response. I figured it out. The ssl.pre from the files section of the code block doesn't get executed and is never created, so i created it manually. Working perfectly. Thanks again.

Created ssl.conf in /etc/httpd/conf.d/ (mod => 644, owner:group => root:root)

<VirtualHost *:443>
	<Directory /opt/python/current/app/build/static>
		Order deny,allow
		Allow from all
	</Directory>
	
	SSLEngine on
	SSLCertificateFile "/etc/letsencrypt/live/${MY_DOMAIN}/fullchain.pem"
	SSLCertificateKeyFile "/etc/letsencrypt/live/${MY_DOMAIN}/privkey.pem"
	SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
	SSLProtocol All -SSLv2 -SSLv3
	SSLHonorCipherOrder On
	SSLSessionTickets Off
	
	Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
	Header always set X-Frame-Options DENY
	Header always set X-Content-Type-Options nosniff
	
	ProxyPass / http://localhost:80/ retry=0
	ProxyPassReverse / http://localhost:80/
	ProxyPreserveHost on
	RequestHeader set X-Forwarded-Proto "https" early
	# If you have pages that may take awhile to
	# respond, add a ProxyTimeout:
	# ProxyTimeout seconds
</VirtualHost>

p.s. don't forget to restart sudo service httpd restart

@vahiwe
Copy link

vahiwe commented Jan 14, 2021

👍 @zedy. You can share for others who might have the same issue.

@caseypage
Copy link

Maybe this will help someone:

I updated my single web instance PHP Platform version and all my https stuff broke because of certbot-auto being deprecated. I'm just using the httpd config files that beanstalk uses by default.

I spent a while trying to get 'certbot' installed by all the different solutions above and elsewhere but kept running into errors/issues.

I was finally able to get it all working using this .ebextension config: https://gist.github.com/caseypage/3f59f29f1fb4d6590c9193340a38ea03

@rich5851
Copy link

rich5851 commented Feb 4, 2021

Maybe this will help someone:

I updated my single web instance PHP Platform version and all my https stuff broke because of certbot-auto being deprecated. I'm just using the httpd config files that beanstalk uses by default.

I spent a while trying to get 'certbot' installed by all the different solutions above and elsewhere but kept running into errors/issues.

I was finally able to get it all working using this .ebextension config: https://gist.github.com/caseypage/3f59f29f1fb4d6590c9193340a38ea03

Thank You! I also ran into issues because of certbot-auto being deprecated and tried different solutions. I was about to give up on this approach until I saw this comment.

@hein-j
Copy link

hein-j commented Mar 21, 2021

@vahiwe I can't thank you enough. Worked perfectly.

@garyng2000
Copy link

for those who is interested, i have created a simple template(for python and nodejs). the EB portion is generic and can be used for other framework(.NET etc.)

https://github.com/garyng2000/flaskweb

@olayinkasf
Copy link

Anyone with a working example of using acme.sh?

@TheArhaam
Copy link

If I'm not mistaken, this is all it takes now:

.ebextensions
|_ 00_epel.config
|_ 01_AWS_Single_LetsEncrypt.config

00_epel.config

commands:
  add_epel_repo:
    command: "sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm"
    test: "[ ! -e /tmp/add_epel_repo_run_once ] && touch /tmp/add_epel_repo_run_once || exit 0"
    ignoreErrors: true

01_AWS_Single_LetsEncrypt.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

packages:
  yum:
    epel-release: []

container_commands:
  01_certbot_install:
    command: "sudo yum install certbot python-certbot-nginx"
  02_certbot_generate_certs:
    command: "sudo certbot --agree-tos --non-interactive --domains ${CERT_DOMAINS} --email ${CERT_EMAIL} --nginx"
  03_certbot_auto_renew:
    command: "sudo certbot renew --dry-run"

@lucas-coelho
Copy link

This will not work, because container_commands runs before Elastic Beanstalk deploys and runs your application and the proxy server. So, the /etc/nginx/nginx.conf will be overridden.
The structure that works for me is shown below:

|-- .platform
|   -- hooks
|       -- postdeploy
|           -- ssl_setup_certbot.sh
|-- .ebextensions
|   -- ssl.config

The content of ssl_setup_certbot.sh:

#!/usr/bin/env bash

echo "Installing CERTBOT.."
CERT_DOMAIN=`/opt/elasticbeanstalk/bin/get-config environment -k CERT_DOMAIN`
CERT_EMAIL=`/opt/elasticbeanstalk/bin/get-config environment -k CERT_EMAIL`
sudo yum -y install certbot python-certbot-nginx
sudo certbot --agree-tos --non-interactive --domains ${CERT_DOMAIN} --email ${CERT_EMAIL} --nginx
sudo certbot renew --dry-run
echo "CERTBOT installed!"

The content of ssl.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

packages:
  yum:
    epel-release: []

files:
  "/etc/cron.d/certbot_renew":
    mode: "000644"
    owner: root
    group: root
    content: |
      0 */12 * * * root /usr/bin/certbot -q renew --nginx
 
commands:
  add_epel_repo:
    command: "sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm"
    test: "[ ! -e /tmp/add_epel_repo_run_once ] && touch /tmp/add_epel_repo_run_once || exit 0"
    ignoreErrors: true
    
   

CERT_DOMAIN and CERT_EMAIL is environment variable included in the eb environment.

@TheArhaam
Copy link

Thanks @lucas-coelho, I'll test that out
I pretty much got exhausted and switched to CodeDeploy 😅

@anhnq-hblab
Copy link

Hi @lucas-coelho.
I'm trying this your config in code but not run https.
can you tell me what config am i missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment