We have a Ruby/Rails website we're migrating from Heroku to AWS. The original dev is not available. I'm now trying to complete the migration. My background is in the Windows / .NET world. This Linux / Ruby/Rails environment is quite foreign to me...
Here's the current environment I've set-up:
Record Name | Record Type | Alias | Alias Route Traffic To |
---|---|---|---|
foo.example.com | A | yes | cloudfront: xyz.cloudfront.net |
Domain Name | Alternate Domain Names | Origin Domain | Origin Protocol | Behavior Protocol |
---|---|---|---|---|
xyz.cloudfront.net | foo.example.com | foo.us-west-2.elb.amazonaws.com | HTTP only | Redirect HTTP to HTTPS |
The CloudFront distribution:
DNS Name | Listener Rule | Forward To |
---|---|---|
foo.us-west-2.elb.amazonaws.com | HTTP 80: default action | Target Group: foo-ec2 |
Target Group: foo-ec2
contains a single Ubuntu ec2 instance running nginx/1.18.0 + Phusion Passenger 6.0.10 to serve up the Ruby/Rails site.
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL Config - we should NEVER receive 443/https traffic;
# CloudFront manages https traffic => AWS ELB => http to this server
#listen 443 ssl default_server;
#listen [::]:443 ssl default_server;
server_name foo.example.com
foo.us-west-2.elb.amazonaws.com;
# Tell Nginx and Passenger where the app's 'public' directory is
root /var/www/foo_example/public;
# Turn on Passenger
passenger_enabled on;
passenger_app_env production;
passenger_ruby /home/ubuntu/.rbenv/versions/2.6.8/bin/ruby;
}
The rails app starts up without error and is served over https. However, when a user attempts to log in / authenticate, the Devise gem sends back a redirect using http and the ELB's DNS name.
Request URL: https://foo.example.com/users/sign_in
Request Method: POST
Status Code: 302
location: http://foo.us-west-2.elb.amazonaws.com/users
server: nginx/1.18.0 + Phusion Passenger(R) 6.0.10
status: 302 Found
Notice the request was over https and our domain:
https://foo.example.com
But now we're over http and the ELB's domain:
http://foo.us-west-2.elb.amazonaws.com
The devise
gem is seeing the host from the ELB and then generates the URL from the ELB host, creating two issues:
I've looked into the devise
documentation to see if we can just pass in the http protocol and domain to use when creating the post back, but my ruby knowledge is limited. Plus, I think this would be the "bad" path; where the "good" path would be to have the AWS ELB forward the actual domain name, instead of it's own.
I've reviewed several SO and related stack sites with similar questions, but I've either ended up with an infinite loop redirect, or the various config changes have resulted in the same behavior of the devise
gem creating the wrong URL post back.
These two questions seem to be the closest, but I'm not quite able to make the "connection" between the answers and my limited knowledge of this ecosystem.
How can I get the AWS ELB to forward our domain, foo.example.com, to the ec2 target group and not the ELB's domain?
After more experimentation with AWS settings, the solution is actually rather simple. The other answers I posted in the question were vague in the actual settings, so here's the concrete solution.
In CloudFront, you need to create a new origin request policy
, not a cache policy
:
The policy will look like this:
Once the new origin request policy has been created:
The behavior will look like this (I'm using no caching for now to verify the ec2 instance is getting all the requests):
That's it. The host header is now correctly passed through to the ELB and ec2 instance. Nothing else needs to be done with the ELB.
I verified the host header was being used in all requests by modifying the nginx logging option to include the $host
variable in the log file (and did a bit more customization to the OOB format):
# prefixed log with '[my-log]', but it's not needed; remove.
log_format my-log '[my-log] $http_x_forwarded_for - $remote_user [$time_local] '
'"$request_method $scheme://$host$request_uri $server_protocol" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time';
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL Config - we should NEVER receive 443/https traffic;
# CloudFront manages https traffic => AWS ELB => http to this server
#listen 443 ssl default_server;
#listen [::]:443 ssl default_server;
server_name foo.example.com
foo.us-west-2.elb.amazonaws.com;
# create the our log file
access_log /var/log/nginx/my-log.access.log my-log;
# Tell Nginx and Passenger where the app's 'public' directory is
root /var/www/foo_example/public;
# Turn on Passenger
passenger_enabled on;
passenger_app_env production;
passenger_ruby /home/ubuntu/.rbenv/versions/2.6.8/bin/ruby;
}
Surely this will help future me as well as others.