How to get Elastic Beanstalk nginx-backed proxy server to auto-redirect from HTTP to HTTPS?
Asked Answered
B

9

60

I've got a Node.js powered site that I'm running on Amazon Elastic Beanstalk.

My Node.js app listens on port 8080, and I'm using the nginx elastic load balancer configuration with my EB app, listening on port 80 and 443 for HTTP and HTTPS.

However, I only want to accept traffic in my app that has come via HTTPS.

I could rig something up in the app to deal with this, but am interested in a way to get the load balancer to redirect all HTTP requests to my site via HTTPS.

Backstop answered 19/6, 2014 at 1:21 Comment(0)
B
84

After several false-starts with ideas from Amazon's paid support, they did come through in the end. The way you get this to work is you configure your environment to respond to both port 80 and 443. Then create a folder in your main Node.js app folder called .ebextensions, and you place a file named 00_nginx_https_rw.config in there, with this text as the contents:

files:
  "/tmp/45_nginx_https_rw.sh":
    owner: root
    group: root
    mode: "000644"
    content: |
      #! /bin/bash

      CONFIGURED=`grep -c "return 301 https" /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf`

      if [ $CONFIGURED = 0 ]
        then
          sed -i '/listen 8080;/a \    if ($http_x_forwarded_proto = "http") { return 301 https://$host$request_uri; }\n' /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf
          logger -t nginx_rw "https rewrite rules added"
          exit 0
        else
          logger -t nginx_rw "https rewrite rules already set"
          exit 0
      fi

container_commands:
  00_appdeploy_rewrite_hook:
    command: cp -v /tmp/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/appdeploy/enact
  01_configdeploy_rewrite_hook:
    command: cp -v /tmp/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact
  02_rewrite_hook_perms:
    command: chmod 755 /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh
  03_rewrite_hook_ownership:
    command: chown root:users /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh

Amazon's support team explained: This config creates a deployment hook which will add the rewrite rules to /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf.

(Previously they had offered me .config's that copied separate files into /etc/nginx/conf.d, but those either had no effect, or worse, seemed to overwrite or take precedence over the default nginx configuration, for some reason.)

If you ever want to undo this, i.e. to remove the hooks, you need to remove this ebextension and issue a command to remove the files that it creates. You can do this either manually, or via ebextensions commands you put in place temporarily:

/opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh
/opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh

I haven't tried this, but presumably something like this would work to remove them and undo this change:

container_commands:
  00_undochange:
    command: rm /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh
  01_undochange:
    command: rm /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh

Hope this can help someone else in the future.

Backstop answered 19/6, 2014 at 1:21 Comment(14)
A followup from AWS customer support prompted me to edit the configuration above. Initially the config was setup to respond with 200 OK if the request was coming from ELB-HealthChecker. This is probably OK for a dev environment, but for production, those requests should be passed through to your app, so that it is being properly pinged. This is now reflected in the config above.Backstop
I love you? I guess I do. There is no guide and little reference to these commands and usage of ebextensions. Glad you ran into the same problem :)Unbolt
@nym Can you expand on why you think this? I am still using this solution with my site and though I haven't re-published the site in several weeks, I hadn't had any trouble with it before then.Backstop
don't work in my case : [error] 9579#0: *7 upstream prematurely closed connection while reading response header from upstream, client: 80.215.204.109, server: , request: "GET /favicon.ico HTTP/1.1", upstream: "http://127.0.0.1:8081/favicon.ico", host: "domainname.fr", referrer: "http://domainname.fr Any suggestion ? I'm using a single instanceFluorine
This solution worked for me. Configure your environment to respond both 80 and 443 ports and after create file suggested by Mason G. Zhwiti.Kolnick
Finally after days of searching, this was the solution! Important Note: Must also configure environment to respond to both http -> 80 and https -> 443. docs.aws.amazon.com/elasticbeanstalk/latest/dg/…Carlock
Using Docker, June 2017. The general solution worked for me, but there were some minor tweaks needed. I'm guessing because ebextensions mess with things that aren't really stable public APIs. Two changes needed: listen 8080; becomes listen 80;, and /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf becomes /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf.Stratification
I'd imagine instead of the cleverness of finding and replacing the existing server block in the existing nginx conf.d file, you could examine the existing nginx config file and just add the return 301 we want and replace the entire file?Theodor
I can confirm, that this is still working with node and ELB. Just as a hint: In my environment variables I use 8081 as port but the port in the config above should still be 8080. Probably some lack of knowledge about nginx on my side, but maybe it helps somebody to fall in the same trapEmmons
After hours of searching, this is the correct answer. finally have http redirecting to https. Thanks for nothing Amazon! Seems like a really common use case. Why isn't there just a "forward http to https" checkbox in the Load balancer settings???Bogosian
You sir are my hero.Sham
You are awesome!Also
@MasonG.Zhwiti thanks for sharing the learnings of your paid Amazon support! 🍺 4 UHellenistic
@Stratification these setting did not work for my single instance docker configuration running node on linux2/3.0.3Hourihan
M
19

The accepted answer no longer worked for me. The default port was a different one. Also the location of the config file has changed. I am setting up a Ruby On Rails application with Puma.

I talked to the paid support, we figured it out by just running the commands manually on the running instance. Then I was able to figure out the below solution. Just by logging in and restarting nginx things then worked.

files:
  "/tmp/45_nginx_https_rw.sh":
    owner: root
    group: root
    mode: "000644"
    content: |
      #! /bin/bash

      CONFIGURED=`grep -c "return 301 https" /opt/elasticbeanstalk/support/conf/webapp_healthd.conf`

      if [ $CONFIGURED = 0 ]
        then
          sed -i '/listen 80;/a \    if ($http_x_forwarded_proto = "http") { return 301 https://$host$request_uri; }\n' /opt/elasticbeanstalk/support/conf/webapp_healthd.conf
          logger -t nginx_rw "https rewrite rules added"
          exit 0
        else
          logger -t nginx_rw "https rewrite rules already set"
          exit 0
      fi

container_commands:
  00_appdeploy_rewrite_hook:
    command: cp -v /tmp/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/appdeploy/enact
  01_configdeploy_rewrite_hook:
    command: cp -v /tmp/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact
  02_rewrite_hook_perms:
    command: chmod 755 /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh
  03_rewrite_hook_ownership:
    command: chown root:users /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh

Notice how I changed the port number and the location of the config file.

Maddiemadding answered 5/1, 2016 at 19:35 Comment(6)
For those wondering, the diff between this config and the accepted answer is the sed line that is appending to /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf after it sees "listen 8080" is now appending to /opt/elasticbeanstalk/support/conf/webapp_healthd.conf after it sees "listen 80". Also the same change in filenames on the CONFIGURED=grep -c line. I am not yet sure why this works for you versus mine, but so not sure yet if this needs to be the accepted answer, or perhaps an alternate answer for a different environment?Backstop
Good comment - I have no idea. The support said that the location for the configs may have changed. I am also using a load balanced version.Maddiemadding
I implemented this solution. It worked. My http request would successfully forward to https. The next day, I tried it and it was back to not forwarding. Any ideas on this?Pentode
Confirming that this worked for me in Ruby on Rails + Puma 2.3 on December 9th 2016. Noting that the original question relates to Node and port 8080 - and not ruby on port 80 - but thanks! Important to restart nginx after this change has been made, so I have one additional configuration file: ``` container_commands: 01_reload_nginx: command: "service nginx reload" ```Disproportion
@j10io I think instead of an additional config file to restart nginx, you can also click "Restart App Server(s)" in the elasticbeanstalk web console.Muniz
Works like a charm! Currently using 64bit Amazon Linux 2017.09 v2.6.5 running Ruby 2.4 (Puma)Mascara
C
9

You could handle the redirect via your Node.js app.

Amazon sends the X-Forwarded-Proto header which equals http when the client has connected insecurely.

The following middleware should be inserted right after initializing Express and before defining your routes to automatically redirect the client to the corresponding HTTPS endpoint:

// Redirect to HTTPS
app.use(function (req, res, next) {
    // Insecure request?
    if (req.get('x-forwarded-proto') == 'http') {
        // Redirect to https://
        return res.redirect('https://' + req.get('host') + req.url);
    }

    next();
});
Crumpet answered 21/3, 2015 at 10:56 Comment(4)
It's worth calling out the tradeoff of doing this at the node.js layer is additional CPU work for your application-tier and slower redirect times.Theodor
@Theodor Thanks for the input!Crumpet
Did not work for us, as we have a single EB instance w/o ELB - Nginx isn't configured to set x-forwarded-protoGarcia
@liridayn If your instance isn't behind an ELB, then you can just check the protocol on the req object by checking req.secure: https://mcmap.net/q/265755/-how-to-know-if-a-request-is-http-or-https-in-node-jsCrumpet
T
6

I was able to get this working with a slightly simpler solution.

Please note, this is an elastic beanstalk deployed SINGLE instance, not load balenced.

This was my ebextension I added.

files:
  "/etc/nginx/conf.d/000_my_config.conf":
    mode: "000755"
    owner: root
    owner: root
    content: |
      server {
          listen 8080;
          return 301 https://$host$request_uri;
      }
Torch answered 2/2, 2015 at 4:24 Comment(9)
why port 8080 rather than 80?Starinsky
I wish I knew...This took a lot of debugging. My guess is that Elastic Beanstalk actually uses port 8080 instead of 80.Torch
Is your environment load balanced or single instance?Starinsky
Single instance, edited the answer to include that fact.Torch
Gotcha, that makes sense. I'm looking for how to do this with a load balanced instance. I've done this, with no success. emind.co/how-to/how-to-force-https-behind-aws-elbStarinsky
@WindUpToy you need to check for the $http_x_forwarded_proto parameter as specified in the accepted answer. The ELB forwards ALL requests to the instances on port 80, which is then seen as HTTP. AWS adds the $http_x_forwarded_proto value and X-Forwarded-Proto header for precisely this check.Resolvable
This finally got me on the right track for the load-balanced solution I needed, see https://mcmap.net/q/330386/-forcing-https-in-elasticbeanstalk-with-certificate-from-acmMuniz
@WindUpToy i think he has elb config as follows: 80 --> 8080 and 443 --> 80 (default-ish) , this is to have on nginx a separate listener for http, and without need to read X-forwarded-Proto header, also so he doesnt have to squash default nginx config already there for port https, this config can be done as documented: docs.aws.amazon.com/elasticbeanstalk/latest/dg/…Wren
The filename here is important. It must lexicographically sort before 00_elastic_beanstalk_proxy.conf (as of today...). nginx accepts the first conflicting server in the first file it finds. You can verify the current filename by shelling into your EC2 instance managed by EB and typing ls /etc/nginx/conf.d.Garcia
C
5

I am running the 'Ruby2 Puma' environment on AWS Elastic Beanstalk which may have a slightly different configuration than above. In my environment I needed to use 'listen 80' instead of 'listen 8080'.

sslredirect.config based on elloworld111's answer:

files:
  "/etc/nginx/conf.d/000_my_config.conf":
    mode: "000755"
    owner: root
    owner: root
    content: |
      server {
          listen 80;
          return 301 https://$host$request_uri;
      }
Coffee answered 18/3, 2015 at 15:29 Comment(0)
F
4

I'm working with Elastic Beanstalk and Docker, so have taken a slightly different route to get things running for me, but very much inspired by the accepted answer. This script injects required config into /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf. (If anyone has a more elegant solution would love to see it)

This script also enables the Beanstalk healthcheck to hit my healthcheck endpoint (in my case api/healthcheck) Better to allow the LoadBalancer to hit the app, rather than terminate at Nginx.

files:
  "/tmp/45_nginx_https_rw.sh":
    owner: root
    group: root
    mode: "000755"
    content: |
      #! /bin/bash

      CONFIGURED=`grep -c "return 301 https" /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf`

      if [ $CONFIGURED = 0 ]
        then
          sed -i "/access.log;/a \ \ \ \ \ \ \ \ location /api/health-check { proxy_pass http://docker; }" /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf
          sed -i "/proxy_add_x_forwarded_for;/a \ \ \ \ \ \ \ \ \ \ \ \ if (\$http_x_forwarded_proto != 'https') { return 301 https://\$host\$request_uri; }" /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf
          logger -t nginx_rw "https rewrite rules added"
          exit 0
        else
          logger -t nginx_rw "https rewrite rules already set"
          exit 0
      fi

container_commands:
  00_run_script:
    command: /tmp/45_nginx_https_rw.sh
Filamentous answered 20/2, 2017 at 21:51 Comment(0)
B
2

I was able to get this to work in a different way. I changed my load balancer to forward port 80 traffic to port 8082, and changed the firewall rules (inbound on the instance, outbound on the firewall) to allow that. And then added this file in .ebextensions:

files:
  "/etc/nginx/conf.d/50-atd-hotel-http-redirect.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      server {
        listen   8082;

        return 301 --WHATEVER DESTINATION YOU WANT--;
      }
Bunin answered 23/2, 2017 at 19:40 Comment(0)
F
0

The accepted answer did not work for me. After many tries (and hours of googling), I find something that did work for me. I too have a Node.js powered site that I'm running on Elastic Beanstalk.

I used the script from here : https://adamjstevenson.com/tutorials/2017/02/02/configuring-and-forcing-https-for-aws-elastic-beanstalk.html

The only modification I did was switch out the

/opt/elasticbeanstalk/support/conf/webapp_healthd.conf

by

/etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf

so it gives this :

files:
  "/tmp/45_nginx_https_rw.sh":
    owner: root
    group: root
    mode: "000644"
    content: |
      #! /bin/bash

      CONFIGURED=`grep -c "return 301 https" /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf`

      if [ $CONFIGURED = 0 ]
        then
          sed -i '/listen 80;/a \    if ($http_x_forwarded_proto = "http") { return 301 https://$host$request_uri; }\n' /etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf
          logger -t nginx_rw "https rewrite rules added"
          exit 0
        else
          logger -t nginx_rw "https rewrite rules already set"
          exit 0
      fi

container_commands:
  00_appdeploy_rewrite_hook:
    command: cp -v /tmp/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/appdeploy/enact
  01_configdeploy_rewrite_hook:
    command: cp -v /tmp/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact
  02_rewrite_hook_perms:
    command: chmod 755 /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh
  03_rewrite_hook_ownership:
    command: chown root:users /opt/elasticbeanstalk/hooks/appdeploy/enact/45_nginx_https_rw.sh /opt/elasticbeanstalk/hooks/configdeploy/enact/45_nginx_https_rw.sh

After eb deploy, just restart your nginx sudo service nginx restart and you're set.

Fullerton answered 21/8, 2019 at 23:26 Comment(0)
S
0

update as of 2023 october

Nowadays, Elastic Beanstalk environments are created with Application Load Balancer (ALB).

You can configure a redirection directly with ALB, no more need to do that with nginx. Follow these steps :

  1. Go in the EC2 console, then in the left menu "load balancers", then click on your ALB.
  2. Open the listener port 80
  3. create a new rule or modify the default rule and choose "redirect URL" with "Full URL". Save.

reference : How can I redirect HTTP requests to HTTPS using an Application Load Balancer?

Spout answered 30/10, 2023 at 14:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.