617

I'm trying to get HTTPS working on express.js for node, and I can't figure it out.

This is my app.js code.

var express = require('express');
var fs = require('fs');

var privateKey = fs.readFileSync('sslcert/server.key');
var certificate = fs.readFileSync('sslcert/server.crt');

var credentials = {key: privateKey, cert: certificate};


var app = express.createServer(credentials);

app.get('/', function(req,res) {
    res.send('hello');
});

app.listen(8000);

When I run it, it seems to only respond to HTTP requests.

I wrote simple vanilla node.js based HTTPS app:

var   fs = require("fs"),
      http = require("https");

var privateKey = fs.readFileSync('sslcert/server.key').toString();
var certificate = fs.readFileSync('sslcert/server.crt').toString();

var credentials = {key: privateKey, cert: certificate};

var server = http.createServer(credentials,function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
});

server.listen(8000);

And when I run this app, it does respond to HTTPS requests. Note that I don't think the toString() on the fs result matters, as I've used combinations of both and still no es bueno.


EDIT TO ADD:

For production systems, you're probably better off using Nginx or HAProxy to proxy requests to your nodejs app. You can set up nginx to handle the ssl requests and just speak http to your node app.js.

EDIT TO ADD (4/6/2015)

For systems on using AWS, you are better off using EC2 Elastic Load Balancers to handle SSL Termination, and allow regular HTTP traffic to your EC2 web servers. For further security, setup your security group such that only the ELB is allowed to send HTTP traffic to the EC2 instances, which will prevent external unencrypted HTTP traffic from hitting your machines.


rmtheis
  • 5,992
  • 12
  • 61
  • 78
Alan
  • 45,915
  • 17
  • 113
  • 134
  • 3
    Answered succinctly here: http://stackoverflow.com/a/23894573/1882064 – arcseldon Oct 09 '14 at 15:32
  • Regarding the last comment on AWS: is it that a server doesn't need to be created with the https module? My certificates are uploaded into AWS via Jenkins and handled with ARN; I have no file paths to use (in https options) – sqldoug Jan 29 '16 at 01:00
  • @sqldoug I'm not sure I understand the question. AWS ELBs can be configured to accept HTTPS connections and act as the SSL termination point. That is, they speak to your app servers via regular HTTP. There typically isn't a reason to have nodejs deal with SSL, because it's just extra processing overhead which can be handled up the stack at either the ELB level or at the HTTP Proxy level. – Alan May 05 '16 at 17:15
  • Thanks Alan; yes I've since realized that Node doesn't need to deal with SSL when AWS ELBs can be so configured. – sqldoug May 06 '16 at 00:14
  • Use ngrok, very easy to use tool to make local port available online via https. Method explained here: https://frontendguruji.com/blog/run-next-js-app-locally-in-https/ – Mandeep Pasbola Aug 25 '22 at 12:14

10 Answers10

911

In express.js (since version 3) you should use the following syntax:

var fs = require('fs');
var http = require('http');
var https = require('https');
var privateKey  = fs.readFileSync('sslcert/server.key', 'utf8');
var certificate = fs.readFileSync('sslcert/server.crt', 'utf8');

var credentials = {key: privateKey, cert: certificate};
var express = require('express');
var app = express();

// your express configuration here

var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);

httpServer.listen(8080);
httpsServer.listen(8443);

In that way you provide express middleware to the native http/https server

If you want your app running on ports below 1024, you will need to use sudo command (not recommended) or use a reverse proxy (e.g. nginx, haproxy).

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
Setthase
  • 13,988
  • 2
  • 27
  • 30
  • 2
    All is written here: https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x Paragraph *Application function* – Setthase Jul 31 '12 at 16:50
  • Yes, you can have http and https using that same server configuration. – Setthase Jul 31 '12 at 16:53
  • 96
    Note that although 443 is the default port for HTTPS, during development you probably want to use something like 8443 because most systems don't allow non-root listeners on low-numbered ports. – ebohlman Aug 01 '12 at 06:48
  • 2
    Man, it works like magic :) It accepts .pem files too, well as it should anyway – Marcelo Ruggeri May 03 '14 at 01:01
  • 5
    express 4 it doesn't work, it works for `localhost:80` but not `https://localhost:443` – Muhammad Umer Feb 22 '15 at 18:56
  • 21
    if you're going to use nginx for reverse proxy, that can handle the ssl certs for you instead of node – Gianfranco P. Nov 03 '15 at 01:41
  • @GianPaJ And what would be the advantage of that? Installing another program instead of writing a few lines? – inf3rno Feb 25 '16 at 07:05
  • @inf3rno nginx is not only for SSL, it handles static content better and is easier for setting caching headers, etc – Gianfranco P. Feb 25 '16 at 15:42
  • @GianPaJ And it handles websockets much worse. – inf3rno Feb 26 '16 at 07:01
  • @codename- In my case, I have two express apps running as frontend and backend on different ports. I am able to enable https on the backend but not sure if follow the same approach. Frontend app runs on port 5000 and backend runs on 4433. Frontend provides login page to the user but all the logic reside in the backend app running on 4433. Please guide me. Thanks – user557657 Aug 22 '17 at 15:21
  • Could any one give hints : curl -v https://localhost:8443/api/v1 * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8443 (#0) * found 148 certificates in /etc/ssl/certs/ca-certificates.crt * found 592 certificates in /etc/ssl/certs * ALPN, offering http/1.1 * SSL connection using TLS1.2 / ECDHE_RSA_AES_128_GCM_SHA256 * server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none * Closing connection 0 curl: (60) server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none – khmub Jan 25 '18 at 10:11
  • 1
    Don't forget to use `https://localhost:8443` instead of http in your browser for development. – GavinBelson Oct 08 '18 at 15:35
  • 1
    `Error: EACCES: permission denied, open '/etc/letsencrypt/live/mysite.com/privkey.pem'` how to solve? – Fandi Susanto Jul 16 '19 at 05:31
  • so the file mode is UTF8? the certificate and secret is written in 8 bit characters? – CDM social medias in bio Dec 05 '19 at 23:57
  • If I want to use variables instead of hard-coding paths like 'sslcert/server.key', I wonder how to get readFileSync working. See problem: https://stackoverflow.com/questions/56744208/fs-readfilesync-cannot-read-file-passing-path-variable-in-nodejs#comment118494792_56744208 – Ryan Apr 10 '21 at 17:22
  • I was able to get this to work but was wondering if it is the right thing to do by having the private key stored on each server? Is there a way to use the .pem file we get when the certificate is generated? – Robel Robel Lingstuyl Jul 12 '22 at 18:56
144

First, you need to create selfsigned.key and selfsigned.crt files. Go to Create a Self-Signed SSL Certificate Or do following steps.

Go to the terminal and run the following command.

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./selfsigned.key -out selfsigned.crt

  • After that put the following information
  • Country Name (2 letter code) [AU]: US
  • State or Province Name (full name) [Some-State]: NY
  • Locality Name (eg, city) []:NY
  • Organization Name (eg, company) [Internet Widgits Pty Ltd]: xyz (Your - Organization)
  • Organizational Unit Name (eg, section) []: xyz (Your Unit Name)
  • Common Name (e.g. server FQDN or YOUR name) []: www.xyz.com (Your URL)
  • Email Address []: Your email

After creation adds key & cert file in your code, and pass the options to the server.

const express = require('express');
const https = require('https');
const fs = require('fs');
const port = 3000;

var key = fs.readFileSync(__dirname + '/../certs/selfsigned.key');
var cert = fs.readFileSync(__dirname + '/../certs/selfsigned.crt');
var options = {
  key: key,
  cert: cert
};

app = express()
app.get('/', (req, res) => {
   res.send('Now using https..');
});

var server = https.createServer(options, app);

server.listen(port, () => {
  console.log("server starting on port : " + port)
});
  • Finally run your application using https.

More information https://github.com/sagardere/set-up-SSL-in-nodejs

Dere Sagar
  • 1,719
  • 1
  • 10
  • 7
  • 3
    Using sudo should be discouraged unless necessary. I just went through this process without using sudo, but I was logged in as admin on the machine. – jhickok Jan 02 '20 at 20:13
  • 2
    The above code is worked in my case I was faced "EACCES: permission denied, open '/etc/letsencrypt/live/domain.net/privkey.pem'” error. I was run `sudo chown ubuntu -R /etc/letsencrypt` and it's worked. ubuntu is user name you can replace it with user name. – javed Dec 28 '20 at 15:35
  • If I want to use variables instead of hard-coding paths like `'/../certs/selfsigned.key'`, I wonder how to get readFileSync working. See problem: https://stackoverflow.com/questions/56744208/fs-readfilesync-cannot-read-file-passing-path-variable-in-nodejs#comment118494792_56744208 – Ryan Apr 10 '21 at 17:23
  • 1
    My issue is that nodejs keeps telling me createServer is not function. my nodejs version is 11.15.0. Any idea? – ThN Jul 20 '21 at 18:03
  • 6
    NET::ERR_CERT_AUTHORITY_INVALID – AlejandroDG Aug 04 '21 at 17:26
  • I'm also getting `NET::ERR_CERT_AUTHORITY_INVALID` when I try to make an http request(CRUD) using react. I think this is related to needing to setup a combined-ca, but I'm not sure how to do this... – Fiddle Freak Aug 08 '21 at 00:53
34

I ran into a similar issue with getting SSL to work on a port other than port 443. In my case I had a bundle certificate as well as a certificate and a key. The bundle certificate is a file that holds multiple certificates, node requires that you break those certificates into separate elements of an array.

    var express = require('express');
    var https = require('https');
    var fs = require('fs');

    var options = {
      ca: [fs.readFileSync(PATH_TO_BUNDLE_CERT_1), fs.readFileSync(PATH_TO_BUNDLE_CERT_2)],
      cert: fs.readFileSync(PATH_TO_CERT),
      key: fs.readFileSync(PATH_TO_KEY)
    };

    app = express()

    app.get('/', function(req,res) {
        res.send('hello');
    });

    var server = https.createServer(options, app);

    server.listen(8001, function(){
        console.log("server running at https://IP_ADDRESS:8001/")
    });

In app.js you need to specify https and create the server accordingly. Also, make sure that the port you're trying to use is actually allowing inbound traffic.

eomoto
  • 341
  • 3
  • 3
  • i have a key and a bundled cert, i am not sure what cert: fs.readFileSync(PATH_TO_CERT), would be and how to "break" the bundled cert, there are like 20+ keys in cert if u ask me :) – Muhammad Umar Sep 21 '17 at 10:24
  • @MuhammadUmar you dont have to break the bundled or even specify it if you dont have one, you will have a bundle cert if applicable, and cert(public key) and key(private key) – Hayden Thring May 30 '19 at 10:22
  • @eomoto thanks bud ! this is the best, you totally nailed the example i needed – Hayden Thring May 30 '19 at 10:22
  • If I want to use variables like `PATH_TO_BUNDLE_CERT_1`, I wonder how to get readFileSync working. See problem: https://stackoverflow.com/questions/56744208/fs-readfilesync-cannot-read-file-passing-path-variable-in-nodejs#comment118494792_56744208 – Ryan Apr 10 '21 at 17:23
  • My issue is that nodejs keeps telling me createServer is not function. my nodejs version is 11.15.0. Any idea? – ThN Jul 20 '21 at 18:03
17

Including Points:

  1. SSL setup
    1. In config/local.js
    2. In config/env/production.js

HTTP and WS handling

  1. The app must run on HTTP in development so we can easily debug our app.
  2. The app must run on HTTPS in production for security concern.
  3. App production HTTP request should always redirect to https.

SSL configuration

In Sailsjs there are two ways to configure all the stuff, first is to configure in config folder with each one has their separate files (like database connection regarding settings lies within connections.js ). And second is configure on environment base file structure, each environment files presents in config/env folder and each file contains settings for particular env.

Sails first looks in config/env folder and then look forward to config/ *.js

Now lets setup ssl in config/local.js.

var local = {
   port: process.env.PORT || 1337,
   environment: process.env.NODE_ENV || 'development'
};

if (process.env.NODE_ENV == 'production') {
    local.ssl = {
        secureProtocol: 'SSLv23_method',
        secureOptions: require('constants').SSL_OP_NO_SSLv3,
        ca: require('fs').readFileSync(__dirname + '/path/to/ca.crt','ascii'),
        key: require('fs').readFileSync(__dirname + '/path/to/jsbot.key','ascii'),
        cert: require('fs').readFileSync(__dirname + '/path/to/jsbot.crt','ascii')
    };
    local.port = 443; // This port should be different than your default port
}

module.exports = local;

Alternative you can add this in config/env/production.js too. (This snippet also show how to handle multiple CARoot certi)

Or in production.js

module.exports = {
    port: 443,
    ssl: {
        secureProtocol: 'SSLv23_method',
        secureOptions: require('constants').SSL_OP_NO_SSLv3,
        ca: [
            require('fs').readFileSync(__dirname + '/path/to/AddTrustExternalCARoot.crt', 'ascii'),
            require('fs').readFileSync(__dirname + '/path/to/COMODORSAAddTrustCA.crt', 'ascii'),
            require('fs').readFileSync(__dirname + '/path/to/COMODORSADomainValidationSecureServerCA.crt', 'ascii')
        ],
        key: require('fs').readFileSync(__dirname + '/path/to/jsbot.key', 'ascii'),
        cert: require('fs').readFileSync(__dirname + '/path/to/jsbot.crt', 'ascii')
    }
};

http/https & ws/wss redirection

Here ws is Web Socket and wss represent Secure Web Socket, as we set up ssl then now http and ws both requests become secure and transform to https and wss respectively.

There are many source from our app will receive request like any blog post, social media post but our server runs only on https so when any request come from http it gives “This site can’t be reached” error in client browser. And we loss our website traffic. So we must redirect http request to https, same rules allow for websocket otherwise socket will fails.

So we need to run same server on port 80 (http), and divert all request to port 443(https). Sails first compile config/bootstrap.js file before lifting server. Here we can start our express server on port 80.

In config/bootstrap.js (Create http server and redirect all request to https)

module.exports.bootstrap = function(cb) {
    var express = require("express"),
        app = express();

    app.get('*', function(req, res) {  
        if (req.isSocket) 
            return res.redirect('wss://' + req.headers.host + req.url)  

        return res.redirect('https://' + req.headers.host + req.url)  
    }).listen(80);
    cb();
};

Now you can visit http://www.yourdomain.com, it will redirect to https://www.yourdomain.com

mgthomas99
  • 5,402
  • 3
  • 19
  • 21
Nishchit
  • 18,284
  • 12
  • 54
  • 81
14

This is how its working for me. The redirection used will redirect all the normal http as well.

const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
var request = require('request');
//For https
const https = require('https');
var fs = require('fs');
var options = {
  key: fs.readFileSync('certificates/private.key'),
  cert: fs.readFileSync('certificates/certificate.crt'),
  ca: fs.readFileSync('certificates/ca_bundle.crt')
};

// API file for interacting with MongoDB
const api = require('./server/routes/api');

// Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Angular DIST output folder
app.use(express.static(path.join(__dirname, 'dist')));

// API location
app.use('/api', api);

// Send all other requests to the Angular app
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist/index.html'));
});
app.use(function(req,resp,next){
  if (req.headers['x-forwarded-proto'] == 'http') {
      return resp.redirect(301, 'https://' + req.headers.host + '/');
  } else {
      return next();
  }
});


http.createServer(app).listen(80)
https.createServer(options, app).listen(443);
  • My issue is that nodejs keeps telling me createServer is not function. my nodejs version is 11.15.0. Any idea? – ThN Jul 20 '21 at 18:02
  • actually I figured out my issue. I had this line `const https = require('https').Server(app);` and tried this `https.createServer(options,app);` That's when I kept getting createServer is not function error. Then, closely looking at examples like yours. I then I understood why I had issue. All I needed was this `const https = require('https');` Thank you for your reply. – ThN Jul 22 '21 at 19:16
13

This answer is very similar to Setthase but its for LetsEncrypt (Ubuntu)

// Dependencies
const fs = require('fs');
const http = require('http');
const https = require('https');
const express = require('express');

const app = express();

// Certificate
const privateKey = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/cert.pem', 'utf8');
const ca = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/chain.pem', 'utf8');

const credentials = {
    key: privateKey,
    cert: certificate,
    ca: ca
};

app.use((req, res) => {
    res.send('Hello there !');
});

// Starting both http & https servers
const httpServer = http.createServer(app);
const httpsServer = https.createServer(credentials, app);

httpServer.listen(80, () => {
    console.log('HTTP Server running on port 80');
});

httpsServer.listen(443, () => {
    console.log('HTTPS Server running on port 443');
});

You might encounter : EACCES: permission denied, open '/etc/letsencrypt/live/yourdeomain.com/privkey.pem'

The answer to that is here : Let's encrypt SSL couldn't start by "Error: EACCES: permission denied, open '/etc/letsencrypt/live/domain.net/privkey.pem'"

What worked for me is this in ubuntu ssh terminal Get user whoami

// Create group with root and nodeuser as members
$ sudo addgroup nodecert
$ sudo adduser ubuntu nodecert
$ sudo adduser root nodecert

// Make the relevant letsencrypt folders owned by said group.
$ sudo chgrp -R nodecert /etc/letsencrypt/live
$ sudo chgrp -R nodecert /etc/letsencrypt/archive

// Allow group to open relevant folders
$ sudo chmod -R 750 /etc/letsencrypt/live
$ sudo chmod -R 750 /etc/letsencrypt/archive

sudo reboot
DragonFire
  • 3,722
  • 2
  • 38
  • 51
10

Use greenlock-express: Free SSL, Automated HTTPS

Greenlock handles certificate issuance and renewal (via Let's Encrypt) and http => https redirection, out-of-the box.

express-app.js:

var express = require('express');
var app = express();

app.use('/', function (req, res) {
  res.send({ msg: "Hello, Encrypted World!" })
});

// DO NOT DO app.listen()
// Instead export your app:
module.exports = app;

server.js:

require('greenlock-express').create({
  // Let's Encrypt v2 is ACME draft 11
  version: 'draft-11'
, server: 'https://acme-v02.api.letsencrypt.org/directory'

  // You MUST change these to valid email and domains
, email: 'john.doe@example.com'
, approveDomains: [ 'example.com', 'www.example.com' ]
, agreeTos: true
, configDir: "/path/to/project/acme/"

, app: require('./express-app.j')

, communityMember: true // Get notified of important updates
, telemetry: true       // Contribute telemetry data to the project
}).listen(80, 443);

Screencast

Watch the QuickStart demonstration: https://youtu.be/e8vaR4CEZ5s

For Localhost

Just answering this ahead-of-time because it's a common follow-up question:

You can't have SSL certificates on localhost. However, you can use something like Telebit which will allow you to run local apps as real ones.

You can also use private domains with Greenlock via DNS-01 challenges, which is mentioned in the README along with various plugins which support it.

Non-standard Ports (i.e. no 80 / 443)

Read the note above about localhost - you can't use non-standard ports with Let's Encrypt either.

However, you can expose your internal non-standard ports as external standard ports via port-forward, sni-route, or use something like Telebit that does SNI-routing and port-forwarding / relaying for you.

You can also use DNS-01 challenges in which case you won't need to expose ports at all and you can also secure domains on private networks this way.

coolaj86
  • 74,004
  • 20
  • 105
  • 125
  • "You can't have SSL certificates on localhost." -- I have SSL working on my React app on localhost. Came here looking for how to make it work in Express. React is my frontend, and Express is my backend. Need it to work for Stripe, since my post to Stripe must be in SSL. Should be obvious, but in localhost I am testing, and on the server it will be production. – Taersious Sep 04 '19 at 19:24
  • 2
    Correction: "You can't have *valid* SSL certificates on localhost". – coolaj86 Sep 05 '19 at 02:00
3

You can also generate a self-signed certificate using node-forge

In the code below, a new certificate is generated on startup, which means you will get a new certificate every time you restart the server.

const https = require('https')
const express = require('express')
const forge = require('node-forge')


;(function main() {
  const server = https.createServer(
    generateX509Certificate([
      { type: 6, value: 'http://localhost' },
      { type: 7, ip: '127.0.0.1' }
    ]), 
    makeExpressApp()
  )
  server.listen(8443, () => {
    console.log('Listening on https://localhost:8443/')
  })
})()


function generateX509Certificate(altNames) {
  const issuer = [
    { name: 'commonName', value: 'example.com' },
    { name: 'organizationName', value: 'E Corp' },
    { name: 'organizationalUnitName', value: 'Washington Township Plant' }
  ]
  const certificateExtensions = [
    { name: 'basicConstraints', cA: true },
    { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
    { name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true },
    { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true },
    { name: 'subjectAltName', altNames },
    { name: 'subjectKeyIdentifier' }
  ]
  const keys = forge.pki.rsa.generateKeyPair(2048)
  const cert = forge.pki.createCertificate()
  cert.validity.notBefore = new Date()
  cert.validity.notAfter = new Date()
  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
  cert.publicKey = keys.publicKey
  cert.setSubject(issuer)
  cert.setIssuer(issuer)
  cert.setExtensions(certificateExtensions)
  cert.sign(keys.privateKey)
  return {
    key: forge.pki.privateKeyToPem(keys.privateKey),
    cert: forge.pki.certificateToPem(cert)
  }
}


function makeExpressApp() {
  const app = express()
  app.get('/', (req, res) => {
    res.json({ message: 'Hello, friend' })
  })
  return app
}
Tobias Bergkvist
  • 1,751
  • 16
  • 20
2

Don't forget your PEM pass phrase in the credentials !

When you generate your credentials with OpenSSL (don't forget the -sha256 flag) :

OpenSSL> req -x509 -sha256 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
Generating a RSA private key

writing new private key to 'key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

Your JS/TS code :

const credentials = {key: privateKey, cert: certificate, passphrase: 'YOUR passphrase'};

Reference

justcodin
  • 857
  • 7
  • 17
-2

This is my working code for express 4.0.

express 4.0 is very different from 3.0 and others.

4.0 you have /bin/www file, which you are going to add https here.

"npm start" is standard way you start express 4.0 server.

readFileSync() function should use __dirname get current directory

while require() use ./ refer to current directory.

First you put private.key and public.cert file under /bin folder, It is same folder as WWW file.

hoogw
  • 4,982
  • 1
  • 37
  • 33