The Problem
I run a simple low-traffic website hosted on Elastic Beanstalk and I did not want to pay for the $15-$20 / month for an auto-scaled environment, which requires an elastic load balancer -- hence the higher cost. Instead, I can run a single EC2 instance and cut my costs in half from ~$30 to ~$15.
The catch is that by dropping the elastic load balancer, you also lose the ability to terminate the SSL (HTTPS) connection and the ability to use Amazon's free SSL certificate service. So what do you do?
The Solution
Summary
We can extend our Elastic Beanstalk application hosting using the EB Extensions features. The following changes will tell Elastic Beanstalk to:
1. get a free SSL certificate from Letsencrypt.org using one of their tools (certbot-auto) which is downloaded and executed after the instance is set up, but before Apache HTTP server is started. The SSL certificates will be stored in /etc/letsencrypt/live folder. The SSL certificate will be valid for 3 months, but you can renew it as often as you need (see step 4).
2. Configure Apache to use those SSL certificates and start listening on port 443 for SSL connections.
3. Updated the security group for the EC2 instance is updated to allow traffic from the internet to port 443.
4. Schedule a cron job to run every month to renew the certificate
Step 1. Create .ebextensions Folder
By including a new folder in the top level of the WAR file called .ebextensions with the following files:
If you are using maven, you need to add the following bit to your pom.xml to get the .ebextension directory to be bundled in the right place in the war:
Step 2. Configure Maven war plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<webResources>
<resource>
<directory>src/main/ebextensions</directory>
<targetPath>.ebextensions</targetPath>
<filtering>true</filtering>
</resource>
</webResources>
</configuration>
</plugin>
Step 3. Add ssl.conf File
This file will update the Apache configuration to look at the SSL certificates generated by letsencrypt.org. Update the domain (in red) for your domain name.Listen 443
<VirtualHost *:443>
ServerName mydomain.com
SSLEngine on
SSLCertificateFile "/etc/letsencrypt/live/www.mydomain.com/fullchain.pem"
SSLCertificateKeyFile "/etc/letsencrypt/live/www.mydomain.com/privkey.pem"
<Proxy *>
Require all granted
</Proxy>
ProxyPass / http://localhost:8080/ retry=0
ProxyPassReverse / http://localhost:8080/
ProxyPreserveHost on
ErrorLog /var/log/httpd/elasticbeanstalk-ssl-error_log
</VirtualHost>
Step 4. Add http-instance-single.config File
Resources:
sslSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
IpProtocol: tcp
ToPort: 443
FromPort: 443
CidrIp: 0.0.0.0/0
commands:
00_getSSLcertificate:
command: |
sudo service httpd stop
mkdir -p /opt/certbot
wget https://dl.eff.org/certbot-auto -O /opt/certbot/certbot-auto;chmod a+x /opt/certbot/certbot-auto
sudo mkdir -p /var/www/challenge
sudo /opt/certbot/certbot-auto certonly --debug --non-interactive --email myEmail@gmail.com --agree-tos --standalone --domains www.mydomain.com --keep-until-expiring -w /var/www/challenge
10_renewSSLCertificate:
command: '(crontab -l ; echo ''0 6 * * * root /opt/certbot/certbot-auto renew --standalone --pre-hook "service httpd stop" --post-hook "service httpd start" --force-renew'') | crontab -'