Using SSL and CloudFront with a single-instance Elastic Beanstalk app
Adding HTTPS to a single-instance Elastic Beanstalk PHP application by putting CloudFront in front of it, including the Apache and Yii2 configuration needed.
A single-instance Elastic Beanstalk environment doesn't terminate SSL the way a load-balanced environment does, so the EC2 serves plain HTTP. You can still put your application behind HTTPS by placing CloudFront in front of it, with an ACM certificate on the distribution, while the origin connection back to the instance stays on HTTP. The wrinkle with a PHP or Yii2 app is that the framework needs to detect the original HTTPS request, which arrives via the CloudFront-Forwarded-Proto header rather than the connection itself.
This picks up once your app is running on Elastic Beanstalk, which is a natural home after migrating a web application to the cloud.
The certificate
Request or import your certificate in AWS Certificate Manager in the US East (N. Virginia) us-east-1 region. CloudFront only uses certificates from that region, regardless of where the rest of your infrastructure lives.
CloudFront distribution settings
Create a distribution with the origin pointing at your Elastic Beanstalk environment domain, for example env.eu-west-2.elasticbeanstalk.com:
- Alternate domain name: your domain, for example
yourdomain.com - Custom SSL certificate: choose the certificate from the list
- Origin: the Elastic Beanstalk environment domain
- Origin protocol: HTTP only, as the EC2 instance has no SSL
- Viewer protocol policy: HTTP and HTTPS
- Allowed HTTP methods: all (GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE)
- Cache based on selected request headers: whitelist
CloudFront-Forwarded-ProtoandHost - Object caching: customise, all TTLs set to 0 if you are not using CloudFront for caching
- Forward cookies: whitelist, adding your Yii or PHP framework CSRF parameter name and session ID
- Query string forwarding and caching: forward all, cache based on all
The .htaccess redirect
Because the request reaching the EC2 instance is always HTTP, an ordinary HTTPS redirect would create a loop. The redirect must instead key off the CloudFront-Forwarded-Proto header, which carries the viewer's original protocol:
RewriteCond %{HTTP:CloudFront-Forwarded-Proto} !https
And to send visitors to your canonical domain:
## Redirect to real domain
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^domain\.test$
RewriteCond %{HTTP_HOST} !^domain\.com$
RewriteRule ^(.*)$ https://domain.com/$1 [R=302,NC]
</IfModule>
Telling Yii2 the request is HTTPS
The redirect handles the browser, but Yii2 still sees a plain HTTP request. Yii::$app->request->isSecureConnection returns false, so absolute URLs from Url::to() and redirects built from the host info come out as http://, and the .htaccess rule then has to bounce each one back to HTTPS.
Since Yii 2.0.13 the request component has a secureProtocolHeaders property listing the headers that mark a connection as secure. The defaults cover X-Forwarded-Proto and Front-End-Https, but not CloudFront's header, so set it in your web config:
'components' => [
'request' => [
'cookieValidationKey' => '...',
'secureProtocolHeaders' => [
'CloudFront-Forwarded-Proto' => ['https'],
],
],
],
Yii's guide recommends also listing a custom header like this in secureHeaders and the proxy's addresses in trustedHosts, so the header is only believed when it comes from the proxy. With CloudFront that means its published IP ranges, which are long and change, so this config skips that step. Yii only filters headers named in secureHeaders, so it still reads CloudFront-Forwarded-Proto, but anyone connecting directly to the Elastic Beanstalk domain could send it too. Don't rely on it for anything more than getting the scheme right.