How to serve a nodeJS app via HTTPS on AWS EC2? - node.js

I'm trying to run a hello world express app on an EC2 instance and serve it via HTTPS.
Here is the server code:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!\n');
});
const server = app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
server.keepAliveTimeout = 65000; // Ensure all inactive connections are terminated by the ALB, by setting this a few seconds higher than the ALB idle timeout
server.headersTimeout = 66000; // Ensure the headersTimeout is set higher than the keepAliveTimeout due to this nodejs regression bug: https://github.com/nodejs/node/issues/27363
I created an EC2 instance and let it run there. Additionally to get HTTPS, I fired up an Application Load Balancer with an SSL certificate. I created a listener on port 443 and forwarded it to port 3000 on my EC2. Lastly I set up a Route53 entry to point to that ALB.
All I get 24/7 is 502 Bad Gateway. Am I missing something basic here?
How to run the most basic express server via HTTPS?

For anyone who might stumble upon this some time later:
If you wish to terminate HTTPS on the load balancer and speak HTTP to your app behind it you need to select HTTP as prototoll and the port of your node app when creating a target group in the console.
For some reason I thought for hours this should be HTTPS and 443 when I want to accept HTTPS traffic.

Related

How do I configure an Elastic Beanstalk NodeJS application with a Classic Load Balancer to use HTTPS?

I have an Elastic Beanstalk application that is running with a Classic Load Balancer. The Load Balancer is using an ACM SSL certificate. The NodeJS app is running an HTTP server on Port 8080, that, when accessed, redirects to the HTTPS server that is running on Port 3000. However, this does not seem to work. Neither server will load anything. Currently, my load balancer is listening on Port 80 and sending to Instance port 80, and listening on Port 443 and sending to instance port 443 with the ACM SSL cert. How should I configure my EB application, Load Balancer, and nodeJS app to make this work?
EDIT: As is default for Elastic Beanstalk applications, there is an nginx instance also running. My bad for not mentioning that.
As per #kgiannakakis's advice, My nodeJS app is now as follows:
var express = require('express');
var path = require('path');
var routes = require("./routes");
const port = process.env.PORT || 8080;
var app = express();
app.set('views', __dirname + '/views');
app.set('view engine', 'pug');
app.set('port',port)
app.use(express.static(path.join(__dirname, 'public')));
app.enable('trust proxy')
app.use((req, res, next) => {
req.secure ? next() : res.redirect('https://' + req.headers.host + req.url)
})
function init() {
app.get('/', routes.index);
app.get("/ping", function(req, res){
res.send("Ok");
res.end();
});
app.listen(app.get('port'), () => {
console.log("App running on port %s.",app.get('port'));
});
}
init();
HTTP requests work fine, however, HTTPS does not.
Since you have an ACM certificate, I am assuming that you are terminating SSL at the Load Balancer level. This means that the communication between the load balancer and the Elastic Beanstalk instances will be unencrypted. For your Node.js server you only need to listen to the proxy port (8080). The details are here. You should note that:
The port that your application listens on doesn't affect the port that the nginx server listens to receive requests from the load balancer.
The default port is 8080 for Amazon Linux 2.
In your Classic Load Balancer you should add two listeners. One for port 80 redirecting to 80 and one to 443 with the certificate, also redirecting to 80. The Nginx server in your instances listens to port 80 and forward the request to your node server.
Please also note that it is possible to terminate SSL at the instances. This requires your own certificate (not through ACM) and properly configuring nginx. Read the details here.

Accessing Express server on EC2

I'm using Express on my Amazon EC2 server. My server looks like this:
var express = require('express');
var app = express();
app.get('/my_view/true', function (req, res) {
//do something
res.render('view', {var1: somevalue});
});
app.listen(3000);
When I access my Express app locally using http://localhost/view/true, it works and I get my template displayed in browser.
But when I try use it on EC2 via ec2-myinstance.us-east-2.compute.amazonaws.com/view/true I get This site can’t be reached. refused to connect error.
I've added HTTP 80 port in my AWS EC2 Security Group settings but it's still not working.
Can someone help?
You should set the server to listen at port 80, instead you are listening at port 3000. So either open up the port 3000 in the security group or listen at the port 80.
app.listen(80);
The above change should work. Let me know if it works.

Custom Port not working for node.js app on AWS EC2 instance

I have deployed the following code on my AWS EC2 instance -
const express = require('express')
const app = express()
app.get('/test',(req,res) => {res.send('Hi')})
app.listen(3001, () => console.log('Server running on port 80'))
When I try to visit the following url - http://ec2-13-59-209-0.us-east-2.compute.amazonaws.com/test , I get connection refused message. The message on the UI is ec2-13-59-209-0.us-east-2.compute.amazonaws.com refused to connect.
I did go through the documentation and set up security group to listen on port 3001. But that did not help either.So I enabled on traffic for all the ports. Still I was not able to connect. Please find below snapshot of the security group. It would be great if you can help me with this.
You need to tell Express to listen to all traffic, not just localhost traffic. Change your app.listen line to:
app.listen(3001, "0.0.0.0");

NodeJS listening on HTTP and HTTPS for one single domain name

I use express and a server cloud on AWS (Amazon Web Server) and a DNS "mydomain.com".
Question: how can I avoid my users to have to writing in the Browser-URL: http://mydomain.com:4000 and https://mydomain.com:3000
This is my code:
sudo node app.js
var app = express();
var server = http.createServer(app).listen(4000, function() {
console.log('Express HTTP server listening on port ' + app.get('port'));
});
var server = https.createServer(credentials, app).listen(3000, function() {
console.log('Express HTTPS server listening on port 3000');
});
// redirect all http requests to https
app.use(function(req, res, next) {
if(!req.secure) {
return res.redirect(['https://mydomain.com', req.url].join(''));
}
next();
});
I want my user to be able to write my domain name using http and https with no port numbers. I already have a SSL certificate and everything is working fine, but I haven't been able remove the port-numbers and use both: https and http.
Any idea? please :)
I use MEAN stack (Mongo, Express, Angular, )
The only way to do that is to use the default ports for the protocols. That is, Port 80 for HTTP and Port 443 for HTTPS.
If you don't use the default protocol ports then the only way for the browser (or whatever client the users are using) to determine which port to connect to is for the user to specify it in the URL.
Edit - To address your comment above about different server objects
In the code in your question you create an HTTP server and then use the variable server to hold a reference to the object. You then create an HTTPS server and assign it to the same variable. If you use the server variable later in your code then you'll be dealing with the HTTPS server object, but will have no way to reference the HTTP server object.
To fix this, just use two different variables to hold the object references.
var httpServer = http.createServer ....
var httpsServer = https.createServer ....

node.js hosting with SSL?

So say I have a node.js application that hosts both a HTTP and HTTPS server as described in the question: How to force SSL / https in Express.js
In my code I have the following:
// General configuration settings for production usage
app.configure(function () {
app.set('port', process.env.PORT || 3000);
app.set('sslport', process.env.SSLPORT || 4000);
...
}
http.createServer(app).listen(app.get('port'), function () {
winston.info('Express server listening on port ' + app.get('port'));
});
var options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
https.createServer(options, app).listen(app.get('sslport'), function () {
winston.info('Express server listening on port ' + app.get('sslport'));
});
Which works perfectly fine for a local running node server.
However, I want to publish my site to a cloud hosted provider like Azure Web Sites, Heroku, Nodejitsu, etc.
All of the cloud hosts seem to set a process.env.PORT value, but only the one. When my HTTPS server is created this usually results in the app crashing, as the PORT is already in use / access denied / etc.
So how do I create / host a site with a secure login page with only one port to work with!?
If you use Heroku you get SSL without needing to specify a port in nodejs. All you need to do is listen on the heroku PORT environment variable for http requests. Once uploaded to heroku you can address your heroku app using either https (on 443) or http (on port 80). Heroku routes either to your server.
Similarly if using elastic load balancing with EC2 you can make use of SSL termination at the load balancer, and again route to your node server listening on port 80 using using http. http://aws.amazon.com/elasticloadbalancing
In both cases you can use either self-signed or proper SSL certificates depending upon your need.

Resources