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.

Mason G. Zhwiti
  • 6,195
  • 8
  • 55
  • 92

8 Answers8

83

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.

Mason G. Zhwiti
  • 6,195
  • 8
  • 55
  • 92
  • 4
    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. – Mason G. Zhwiti Jun 20 '14 at 18:43
  • 9
    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 :) – JehandadK Mar 19 '15 at 11:27
  • @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. – Mason G. Zhwiti May 25 '15 at 15:28
  • 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 instance – Gura Dec 10 '15 at 09:32
  • 4
    This solution worked for me. Configure your environment to respond both 80 and 443 ports and after create file suggested by Mason G. Zhwiti. – Markenson França Apr 03 '16 at 18:08
  • Finally after days of searching, this was the solution! Important Note: Must also configure environment to respond to both http -> 80 and https -> 443. http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.managing.elb.html – Gabe Karkanis Jan 15 '17 at 20:54
  • 5
    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`. – waffleau Jun 02 '17 at 12:21
  • 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? – tom Aug 29 '17 at 09:04
  • 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 trap – jkettmann Jan 05 '18 at 10:54
  • 1
    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??? – Eric Rowell Apr 29 '18 at 08:40
  • 1
    You sir are my hero. – Nicholas Hrboka Jun 04 '18 at 22:08
  • You are awesome! – Peter Porfy Jul 20 '18 at 13:38
  • @MasonG.Zhwiti thanks for sharing the learnings of your paid Amazon support! 4 U – Gus Crawford Aug 01 '18 at 18:21
  • @waffleau these setting did not work for my single instance docker configuration running node on linux2/3.0.3 – Ariel Frischer Jun 27 '20 at 04:06
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.

Hendrik
  • 4,481
  • 5
  • 43
  • 49
  • 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? – Mason G. Zhwiti Jan 05 '16 at 20:03
  • 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. – Hendrik Jan 05 '16 at 20:09
  • 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? – user2517182 Sep 25 '16 at 15:40
  • 2
    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" ``` – j10io Dec 09 '16 at 01:48
  • @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. – mb21 Apr 12 '17 at 10:03
  • Works like a charm! Currently using 64bit Amazon Linux 2017.09 v2.6.5 running Ruby 2.4 (Puma) – Juan Fuentes Feb 12 '18 at 01:26
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();
});
Elad Nava
  • 6,524
  • 2
  • 36
  • 59
  • 2
    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. – tom Aug 29 '17 at 09:02
  • @tom Thanks for the input! – Elad Nava Aug 30 '17 at 20:10
  • Did not work for us, as we have a single EB instance w/o ELB - Nginx isn't configured to set `x-forwarded-proto` – Iiridayn Nov 14 '18 at 00:30
  • @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://stackoverflow.com/a/16405622/1123355 – Elad Nava Nov 14 '18 at 04:00
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;
      }
elloworld111
  • 255
  • 1
  • 4
  • 10
  • why port 8080 rather than 80? – winduptoy Mar 06 '15 at 02:44
  • 2
    I wish I knew...This took a lot of debugging. My guess is that Elastic Beanstalk actually uses port 8080 instead of 80. – elloworld111 Mar 06 '15 at 16:42
  • Is your environment load balanced or single instance? – winduptoy Mar 10 '15 at 18:06
  • Single instance, edited the answer to include that fact. – elloworld111 Mar 10 '15 at 18:41
  • Gotcha, that makes sense. I'm looking for how to do this with a load balanced instance. I've done this, with no success. http://www.emind.co/how-to/how-to-force-https-behind-aws-elb – winduptoy Mar 10 '15 at 18:51
  • 1
    @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. – mwotton Jun 22 '15 at 07:54
  • This finally got me on the right track for the load-balanced solution I needed, see http://stackoverflow.com/a/43365175/214446 – mb21 Apr 12 '17 at 09:11
  • @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: http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environments-cfg-applicationloadbalancer.html – abualy Jun 03 '17 at 08:08
  • _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`. – Iiridayn Nov 14 '18 at 00:56
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;
      }
Community
  • 1
  • 1
Adam D
  • 51
  • 1
  • 3
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
Iain Hunter
  • 2,795
  • 1
  • 20
  • 10
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--;
      }
RobO
  • 41
  • 2
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.

Matija Martic
  • 213
  • 2
  • 10