10

I'm creating a an HTTPS server for the first time in Node, and the code (see below) works for a random port like 6643 but on port 443, it won't work. I get this error:

[Debug][Server]: Initialized...
[Debug][Control Center]: Application initialized...

events.js:72
        throw er; // Unhandled 'error' event
              ^
Error: listen EACCES
    at errnoException (net.js:904:11)
    at Server._listen2 (net.js:1023:19)
    at listen (net.js:1064:10)
    at Server.listen (net.js:1138:5)
    at Object.module.exports.router (/home/ec2-user/Officeball/Versions/officeball_v0.0.5/server/custom_modules/server.js:52:5)
    at Object.<anonymous> (/home/ec2-user/Officeball/Versions/officeball_v0.0.5/server/control_center.js:15:59)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)

This is on an Amazon Linux EC2 server. It's my understanding that once I set my domain's DNS A Name Record to the server's IP, when a user searches https://mydomain.com, the browser will look up my server's IP at port 443, which is supposedly the standard port for HTTPS traffic.

So my understanding is that I need to serve https content via port 443.

What am I doing wrong?


Here's my server code:

control_center.js (init)

/* Control Center */

//DEFINE GLOBALS

preloaded = {};

//GET DIRECT WORKING PATH

var dirPath = process.cwd();

//REQUIRE CUSTOM MODULES 

var debug = new (require(dirPath + 
        "/custom_modules/debug"))("Control Center");
var socket = require(dirPath + 
        "/custom_modules/socket")(4546);

// ! this is the relevant line
var server = require(dirPath + "/custom_modules/server").router(443);

//APP INITIALIZE

debug.log("Application initialized...");

server.js

/* Server */

//REQUIRE NPM MODULES

var fs      = require('fs'), 
    https   = require('https'), 
    url     = require('url'), 
    path    = require('path');

//GET DIRECT WORKING PATH

var dirPath = process.cwd();

//REQUIRE CUSTOM MODULES

//Snip!

var debug = new (require(dirPath + 
        "/custom_modules/debug"))("Server");

//Preload requests

var preload = require(dirPath + 
        '/custom_modules/preload').init();

//INIT MODULE 

debug.log("Initialized...");

//DEFINE MODULE VARIABLES

var options = {
  key: fs.readFileSync('SSL/evisiion_private_key.pem'),
  cert: fs.readFileSync('SSL/evisiion_ssl_cert.pem')
};

//LISTEN FOR PATH REQUESTS

//route requests to server
module.exports.router = function(port) {
    https.createServer(options, function(req, res) {

        //Snip!

    }).listen(port);
};
  • Probably not the code, take a look [here](http://stackoverflow.com/questions/5004159/opening-port-80-ec2-amazon-web-services/10454688#10454688). – Brendan Oct 26 '14 at 23:17
  • @BrendanAshworth when it's so simple that it hurts... –  Oct 26 '14 at 23:18
  • only reason I knew was because I've answered the same question before :) also might have to do with root user priviledges if that answer didn't fix it – Brendan Oct 26 '14 at 23:19
  • 1
    See [here](http://stackoverflow.com/questions/16573668/best-practices-when-running-node-js-with-port-80-ubuntu-linode) for an iptable workaround that allows ports below 1024 to operate when not running root. Also see [here](http://serverfault.com/questions/602240/how-to-run-node-js-app-on-port-80-are-processes-blocking-my-port) for a near duplicate question with answers. – jfriend00 Oct 26 '14 at 23:52

1 Answers1

32

On Linux (and, I believe, most other Unix-like operating systems), a service has to run as root to be able to bind to a port numbered less than 1024.

I've just verified it on a Node app I had lying around, and I saw exactly the same error, line for line identical barring the file paths, when I changed the port from 5000 to 443.

In development, most people will run the dev server on a higher-numbered port such as 8080. In production, you might well want to use a proper web server such as Nginx to serve static content and reverse proxy everything else to your Node app, which makes it less of an issue since Nginx can be quite happily run as root.

EDIT: As your use case requires serving some static content, then you may want to use a web server such as Nginx or Apache to handle the static files, and reverse proxy to another port for your dynamic content. Reverse proxying is quite straightforward with Nginx - here's a sample config file:

server {
    listen 443;
    server_name example.com;
    client_max_body_size 50M;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /static {
        root /var/www/mysite;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
    }
}

This assumes your web app is to be accessible on port 443, and is running on port 8000. If the location matches the /static folder, it is served from /var/www/mysite/static. Otherwise, Nginx hands it off to the running application at port 8000, which might be a Node.js app, or a Python one, or whatever.

This also quite neatly solves your issue since the application will be accessible on port 443, without having to actually bind to that port.

It should be said that as a general rule of thumb running a service like this as root isn't a good idea. You'd be giving root access to an application which might potentially have vulnerabilities, and to any NPM modules you've pulled in. Putting it behind, for example, Nginx will mean you don't need to run it as root, and Nginx is solid and well-tested, as well as having solid performance and caching capability. In addition, Nginx will usually be faster at serving static content in particular.

Matthew Daly
  • 9,212
  • 2
  • 42
  • 83
  • You were right, just threw in the magic word and it worked right away. Gotta love `sudo` –  Oct 26 '14 at 23:51
  • 3
    @jt0dd - it is generally a bad practice to run your web server at root access as it multiplies the number of security vulnerabilities you could be subject to. See the two posts in my comment on your question for alternate ways of listening on a low port without running your node process as root. I personally am using the iptable solution. – jfriend00 Oct 26 '14 at 23:55
  • @jfriend00 Just beat me to it! The iptables solution is a good one as long as you don't need to serve any static files. If you do, I think it's better to do what I mentioned - serve them with Nginx and reverse proxy everything else to the port the Node.js application is running on. – Matthew Daly Oct 26 '14 at 23:59
  • @MatthewDaly what do you mean by "static files"? –  Oct 27 '14 at 00:00
  • @jt0dd Normally this will be CSS, client-side JavaScript or images – Matthew Daly Oct 27 '14 at 00:01
  • @MatthewDaly That's 90% of what I'm serving. What are the consequences of serving static files that make it worth studying nginx and all of that complicated sounding stuff you mentioned instead of the iptables solution? –  Oct 27 '14 at 00:03
  • 1
    @jt0dd Well, web servers like Apache and Nginx are solid, widely used products that are likely to perform signficantly better and are more secure than any custom solution most of us can create on our own. Nginx is generally accepted to be faster than Apache for serving static content, making it ideal for this use case. It also nicely solves the issue in this case by allowing you to run Nginx on the required port. Nginx is quite easy to configure - I'll add a sample config to my answer. – Matthew Daly Oct 27 '14 at 00:13
  • 1
    IMO, you should only add complexity to your setup if you actually have data that you need an extra web server layer to serve static files. I have a site that it simply isn't needed for (node works just just fine by itself), so in that case, simpler is better. The OP should keep things simple unless more complexity is actually needed. The iptables solution solves the port issue without root privileges. – jfriend00 Oct 27 '14 at 00:35
  • It should be noted you *can* drop from root privileges after listening on the port via `process.setgid()` and `process.setuid()`. – mscdex Oct 27 '14 at 01:09
  • @mscdex nice. That's even easier. –  Oct 27 '14 at 02:13
  • Can you guys show any articles or numbers backing the idea that node is significantly slower at serving files? I'd be willing to bet $1.28 that it's very close to the same speed if not faster. –  Oct 27 '14 at 02:15
  • There's http://stackoverflow.com/questions/16770673/using-node-js-only-vs-using-node-js-with-apache-nginx . I think the main advantage of serving files with Nginx is probably that it already handles HTTP headers for you, so you can more easily leverage browser caching of static files. Node can do this, but you need to implement it yourself. – Matthew Daly Oct 27 '14 at 07:34
  • I should also mention that by using Nginx as a reverse proxy, you can implement compression of your response quickly and easily. To do so with Node, you'd need to implement it yourself (unless there's some middleware already available). – Matthew Daly Nov 11 '14 at 11:28
  • I know this was three years ago now, but the creators of Express [explicitly recommend using a reverse proxy for static content over serving it with Express](https://expressjs.com/en/advanced/best-practice-performance.html#use-a-reverse-proxy). – Matthew Daly Nov 14 '17 at 10:09
  • `setcap` and `authbind` can allow non-root processes to set ports. [reference](https://superuser.com/questions/710253/allow-non-root-process-to-bind-to-port-80-and-443) – Herbert Feb 03 '20 at 12:57
  • @Herbert Regardless of whether it's possible, it's still a bad idea. Sticking it behind Nginx is nearly always going to be more performant and more secure. – Matthew Daly Feb 03 '20 at 15:14
  • @MatthewDaly If it is a bad idea, then that should be written in this answer. Now it states "On Linux a service has to run as root to be able to bind to a port numbered less than 1024." This is a nice approximation, but not true. – Herbert Feb 03 '20 at 16:09