How we configured Elastic Beanstalk to play nicely with automatic certificate renewal by Let’s Encrypt.
Everyone it seems is going with Let’s Encrypt to generate their free SSL/TLS ceritficate. Running it once is easy, but getting it configured to work with Elastic Beanstalk and EC2’s lifecycle can send you round in circles. This post is an update of the original January 2017 post with our improved configuration.
The configuration needs to cater for ALL of these scenarios:
-
The website is deployed and encrypted. - We are replacing the certificate with a Let’s Encrypt certificate before it expires.
-
The website is deployed and encrypted but with an expired certificate. - The website is no longer accessible.
-
The website is deployed and encrypted but with a missing certificate. - For example a new EC2 instance has been created.
The events we have at our disposal are:
- At Deployment
- On a Cron schedule
We are deploying a Rails app to a single EB instance. In the following sustitute ‘mywebsite’ with your website name.
Part 1) Add an Elastic Beanstalk post deploy hook
Configure Elastic Beanstalk to install and run certbot everytime the website is deployed. This ensures NGINX is running and everything is in place.
To do this we hook into the Elastic Beanstalk deployment sequence
with a purposely named script that will run after the website is deployed
/opt/elasticbeanstalk/hooks/appdeploy/post/99_run_certbot_config.sh
-
Install certbot:
mkdir -p /var/log/certbot chown ec2-user:ec2-user /var/log/certbot cd /home/ec2-user unset PYTHON_INSTALL_LAYOUT wget https://dl.eff.org/certbot-auto --output-file=/var/log/certbot/install.log chmod a+x certbot-auto
-
Run certbot:
./certbot-auto certonly --debug --non-interactive --agree-tos --email mail@mywebsite.com --webroot --webroot-path /var/app/current/public --domains 'www.mywebsite.com' --text >> /var/log/certbot/install.log
-
Tidyup:
Currently certbot adds a -0001 folder when a certificate already exists, so there is some directory moving to be done before we reload the NGINX server to pick up changes to its configuration.
DIRECTORY="/etc/letsencrypt/live/www.mywebsite.com" if [ -d "$DIRECTORY-0001" ]; then mv $DIRECTORY $DIRECTORY-old mv $DIRECTORY-0001 $DIRECTORY fi service nginx reload
-
Logging:
We have introduced some new logs to capture output, so we can configure Elastic Beanstalk to include these when we fetch the logs.
Putting it all together:
# .ebextensions/certbot.config
# Include certbot/logs in aws logs fetch
files:
"/opt/elasticbeanstalk/tasks/taillogs.d/cloud-init.conf" :
mode: "000755"
owner: root
group: root
content: |
/var/log/certbot/*.log
/var/log/letsencrypt/letsencrypt.log
"/opt/elasticbeanstalk/tasks/bundlelogs.d/applogs.conf" :
mode: "000755"
owner: root
group: root
content: |
/var/log/certbot/*.log
/var/log/letsencrypt/letsencrypt.log
# Install & run certbot
"/opt/elasticbeanstalk/hooks/appdeploy/post/99_run_certbot_config.sh":
mode: "000755"
owner: root
group: root
content: |
mkdir -p /var/log/certbot
chown ec2-user:ec2-user /var/log/certbot
cd /home/ec2-user
echo "=======|| $(date) START wget https://dl.eff.org/certbot-auto ||=======" >> /var/log/certbot/install.log
unset PYTHON_INSTALL_LAYOUT
wget https://dl.eff.org/certbot-auto --output-file=/var/log/certbot/install.log
chmod a+x certbot-auto
echo "=======|| $(date) START ./certbot-auto certonly ||=======" >> /var/log/certbot/install.log
./certbot-auto certonly --debug --non-interactive --agree-tos --email enquiries@mywebsite.com --webroot --webroot-path /var/app/current/public --domains 'www.mywebsite.com' --text >> /var/log/certbot/install.log
echo "=======|| $(date) END ./certbot-auto certonly ||=======" >> /var/log/certbot/install.log
DIRECTORY="/etc/letsencrypt/live/www.mywebsite.com"
if [ -d "$DIRECTORY-0001" ]; then
mv $DIRECTORY $DIRECTORY-old
mv $DIRECTORY-0001 $DIRECTORY
fi
service nginx reload
Part 2) Configure NGINX
-
Open up the challenge route on port 80
In your NGINX config file, add an HTTP server section that opens the challenge route, but redirects for everything else:
// etc/nginx/conf.d/ssl.conf ... # HTTP server server { listen 8080; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; server_name www.mywebsite.com; # enables Lets encrypt location /.well-known { allow all; } location / { return 301 https://$server_name$request_uri; } } ...
-
Update the ssl_certificate & ssl_certificate_key locations
// etc/nginx/conf.d/ssl.conf ... # HTTPS server server { listen 443; ... # SSL ssl on; ssl_certificate /etc/letsencrypt/live/www.mywebsite.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.mywebsite.com/privkey.pem; ... add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; ... } ...
Part 3) Configure a Cron schedule
Add a cron schedule that auto renews every so often. This will run a script that includes the tidyup and NGINX reload.
# .ebextensions/cron.config
files:
"/tmp/cron_certbot_renew.sh" :
mode: "000777"
content: |
/home/ec2-user/certbot-auto renew --text >> /var/log/certbot/cron.log
DIRECTORY="/etc/letsencrypt/live/www.mywebsite.com"
if [ -d "$DIRECTORY-0001" ]; then
mv $DIRECTORY $DIRECTORY-old
mv $DIRECTORY-0001 $DIRECTORY
fi
service nginx reload
"/tmp/cron_jobs" :
mode: "000777"
content: |
52 17 * * * sudo bash /tmp/cron_certbot_renew.sh >> /var/log/certbot/cron.log
container_commands:
01_remove_old_cron_jobs:
command: "crontab -r || exit 0"
02_cronjobs:
command: "crontab /tmp/cron_jobs"
leader_only: true
With this all in place you can deploy or re-deploy to add a certificate to a new instance; and let Cron take care of renewals.