AWS Elastic Beanstalk is powerful deployment tools on AWS. It allows users to create applications and push them to a definable set of AWS services, including Amazon EC2, Amazon RDS, Amazon Simple Notification Service (SNS), Amazon CloudWatch, Auto Scaling Group, and Elastic Load Balancer (ELB). The problem is: Elastic Beanstalk Web Console is not so powerful. But, it can be extended using script (.ebextensions
) and CLI.
Problem: HTTP and HTTPS Configuration on Elastic Beanstalk (Web Console)
From the Elastic Beanstalk Web Console, you can configure a web application to listen HTTP and HTTPS port using Elastic Load Balancer (ELB). But, the ELB will forward/proxy the request into a single HTTP port. It means that HTTP and HTTPS will serve same response from user point-of-view. HTTPS connection is terminated (HTTPS-termination) in ELB.
Some users (at least me and some users in stackoverflow.com/serverfault.com) want:
- HTTP request is replied with HTTP redirection (
3xx
status code) to HTTPS. - HTTPS request is replied by actual web app.
- No HTTPS-termination in ELB.
Solution: Configure Elastic Load Balancer using .ebextentions
Some of Elastic Beanstalk resources can be customized using .ebextenstions
script (see: Customize Containers and Environment Resources). Now, we will configure the ELB to proxy HTTP and HTTPS request to different EC2 instance’s ports.
- Create
.ebextensions
directory inside your app root path. - Create a file (e.g:
00-load-balancer.config
) inside.ebextensions
directory. - Write the following configuration into the file (
.ebextensions/00-load-balancer.config
).
{ "Resources": { "AWSEBSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Allow HTTP and HTTPS", "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "CidrIp": "0.0.0.0/0" }, { "IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "CidrIp": "0.0.0.0/0" } ] } }, "AWSEBLoadBalancerSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Allow HTTP and HTTPS", "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "CidrIp": "0.0.0.0/0" }, { "IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "CidrIp": "0.0.0.0/0" } ], "SecurityGroupEgress": [ { "IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "CidrIp": "0.0.0.0/0" }, { "IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "CidrIp": "0.0.0.0/0" } ] } }, "AWSEBLoadBalancer": { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "HealthCheck": { "HealthyThreshold": "3", "Interval": "30", "Target": "HTTP:80/status.html", "Timeout": "5", "UnhealthyThreshold": "5" }, "Listeners": [ { "LoadBalancerPort": 80, "Protocol": "HTTP", "InstancePort": 80, "InstanceProtocol": "HTTP" }, { "LoadBalancerPort": 443, "Protocol": "HTTPS", "InstancePort": 443, "InstanceProtocol": "HTTPS", "SSLCertificateId": "arn:aws:iam::123456789012:server-certificate/YourSSLCertificate" } ], "SecurityGroups": [ { "Fn::GetAtt": [ "AWSEBLoadBalancerSecurityGroup", "GroupId" ] } ] } } } }
In the above config, we modified 3 resources:
- EC2 Instance Security Group, allow to listen on port HTTP (80) and HTTPS (443).
- ELB Security Group, allow to listen on port HTTP (80) and HTTPS (443).
- ELB, we modified ELB to:
- Do health check EC2 instances on port 80 by HTTP request to /status.html. So, we need to create a hole in port HTTP to allow access the page (will be described later). Elastic Beanstalk doesn’t allow us to do health check using HTTPS request. If you want to do health check by checking TCP port 80, just remove this config section.
- Make ELB listen to port 80 and forward it to EC2 instance’s port 80.
- Make ELB listen to port 443 and forward it to EC2 instance’s port 443.
Now the ELB configuration is ready. But, we need to configure web server inside EC2 instances.
Elastic Beanstalk provides some different type of environment (e.g: Java, Python, Ruby, Docker, etc.). Each environment might have different configuration. You can check it on Supported Platforms. At the time of writing this post, they use some web proxy/server (i.e. Apache 2.2, Apache 2.4, Nginx 1.6.2 and IIS 8.5) to listen at port 80 (HTTP).
In this post, I only tell you how to configure Single Docker Container Elastic Beanstalk, which is using Nginx 1.6.2 as proxy. Basically, Single Docker Container Elastic Beanstalk use Nginx to proxy the request to a Docker container. Each time you deploy a new update, Elastic Beanstalk agent inside EC2 instance will update Docker upstream in /etc/nginx/conf.d/elasticbeanstalk-nginx-docker-upstream.conf
. Another environment can be configured slightly same.
- Create a file (e.g:
01-nginx-proxy.config
) inside.ebextensions
directory. - Write the following configuration into the file (
.ebextensions/01-nginx-proxy.config
). Don’t forget to adjust some config (e.g: domain name, SSL certificate, etc.).
files: "/etc/nginx/sites-available/000-default.conf": mode: "000644" owner: root group: root content: | map $http_upgrade $connection_upgrade { default "upgrade"; "" ""; } server { listen 80; server_name your-domain.com; location = /status.html { proxy_pass http://docker; proxy_http_version 1.1; proxy_set_header Connection $connection_upgrade; proxy_set_header Upgrade $http_upgrade; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; } location / { return 301 https://$host$request_uri; } } server { listen 443; ssl on; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_certificate /opt/ssl/default-ssl.crt; ssl_certificate_key /opt/ssl/default-ssl.pem; ssl_session_cache shared:SSL:10m; location / { proxy_pass http://docker; proxy_http_version 1.1; proxy_set_header Connection $connection_upgrade; proxy_set_header Upgrade $http_upgrade; 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 X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; } } "/opt/ssl/default-ssl.crt": mode: "000400" owner: root group: root content: | -----BEGIN CERTIFICATE----- * * YOUR-CHAINED-SSL-CERTIFICATE-HERE * -----END CERTIFICATE----- "/opt/ssl/default-ssl.pem": mode: "000400" owner: root group: root content: | -----BEGIN RSA PRIVATE KEY----- * * YOUR-SSL-PRIVATE-KEY-HERE * -----END RSA PRIVATE KEY----- commands: 00_enable_site: command: 'rm -f /etc/nginx/sites-enabled/* && ln -s /etc/nginx/sites-available/000-default.conf /etc/nginx/sites-enabled/000-default.conf'
In the above config, we:
- Create SSL certificate and key file.
- Create Nginx site config:
- Listen port 80 (HTTP) and redirect all request to HTTPS, except for /status.html. We create a hole here to allow load balancer do health check.
- Listen port 443 (HTTPS) and proxy the request to actual web server (in this case, Docker container upstream, http://docker).
- Remove all enabled-sites config and create symlink for the new Nginx config.
After that, you can zip your app directory and deploy it to Elastic Beanstalk via Web Console or CLI.
Thanks so much for this.
LikeLike
You’re welcome
LikeLike
Hey, I’m using AWS Elastic beanstalk and my reqirement is same. But I’m using tomcat which is proxied by httpd. my elasticbeanstalk.conf file (/etc/httpd/conf.d/elastickbeanstalk.conf) looks like this:
Order deny,allow
Allow from all
ProxyPass / http://localhost:8080/ retry=0
ProxyPassReverse / http://localhost:8080/
ProxyPreserveHost on
ErrorLog /var/log/httpd/elasticbeanstalk-error_log
I have enabled https on elastickbeanstalk from console and it’s working (ie. when i try to reach the site using https, it works) but when I try to reach with http it’s not redirecting me to https.
I tried following:
Order deny,allow
Allow from all
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} ^/health
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
ProxyPass / http://localhost:8080/ retry=0
ProxyPassReverse / http://localhost:8080/
ProxyPreserveHost on
ErrorLog /var/log/httpd/elasticbeanstalk-error_log
But it didn’t work… can you help me with this? thanks.
LikeLike
Try to remove:
RewriteCond %{REQUEST_URI} ^/health
from your Apache config. It makes
RewriteRule
is only applied to request which started with/health
.Or, you can make it:
RewriteCond %{REQUEST_URI} !^/health
This one is to apply
RewriteRule
to all request except started with/health
.LikeLike
I think it is a bad idea to store ssl certificates in config files like .ebextensions/01-nginx-proxy.config (it is potential security breach if your repo will be compromised).
There are other ways to set up this stack.
LikeLike
Hi Dmytro, thanks for your security consideration.
Usually, I put the SSL certs into a more secure storage, like S3 bucket with tight security for this requirement.
But, if we don’t need to encrypt the connection between ELB and EC2 instances, we can just put the certificate in ELB, no need to copy those certs into EC2.
What do you think?
LikeLike
Thank you for the trick! Does it works on a single instance without the load balancer in front of it ?
Thanks in advance
LikeLike
Yes, it should be able to. But you need to adjust some configuration related to ELB.
LikeLike
Hey, thanks a lot. I have a question. I am using AWS Certificate Manager to generate my SSL certificate. How do I use it to edit the SSL/TLS configurations that you have in your .ebextensions/01-nginx-proxy.config file?
LikeLike
If you want to use AWS Certificate Manager, you just need to attach the certificate to AWS Elastic Load Balancer. No need to install in Nginx.
LikeLike
Thank you very much for this! Very smart and well explained.
Could you tell me what to modify in these scripts in order to have the HTTPS termination in ELB?
Thanks
LikeLike
I got it working by replacing
http://docker
byhttp://docker/
otherwise I had 404 errors.I don’t underdstand why you added the line
server_name
. Can’t we remove it?LikeLike
Hey Edward :- Any help on this
1. Create an ELB
a. Listen on
i. HTTP Port 80
ii. HTTPS Port 443
1. Terminate SSL here
LikeLike
Where do I find the ssl cert and rsa key?
Thank you.
LikeLike
Hi Edward,
How do we tackle this issue when in a multi-container docker in Beanstalk. Where one only wants their web server container to be able to redirect HTTP to HTTPS while rest of the containers are accessible via HTTPS only.
Do I just add the ngnix conf only for my web server code under its .ebextensions?
I dont think ngnix is provided when a multi container docker is launched via Beanstalk.
Thanks!
LikeLike
Hi Edward,
In your 01-nginx-proxy.config file you mentioned the two below. Where can I find them in AWS please? What do they look like?
YOUR-CHAINED-SSL-CERTIFICATE-HERE
YOUR-SSL-PRIVATE-KEY-HERE
Thanks
LikeLike