264

I am setting up my first Node.js server on a cloud Linux node and I am fairly new to the details of Linux admin. (BTW I am not trying to use Apache at the same time.)

Everything is installed correctly, but I found that unless I use the root login, I am not able to listen on port 80 with node. However I would rather not run it as root for security reason.

What is the best practice to:

  1. Set good permissions / user for node so that it is secure / sandboxed?
  2. Allow port 80 to be used within these constraints.
  3. Start up node and run it automatically.
  4. Handle log information sent to console.
  5. Any other general maintenance and security concerns.

Should I be forwarding port 80 traffic to a different listening port?

Thanks

mujaffars
  • 1,327
  • 12
  • 33
Robotbugs
  • 3,947
  • 3
  • 19
  • 28

4 Answers4

545

Port 80

What I do on my cloud instances is I redirect port 80 to port 3000 with this command:

sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000

Then I launch my Node.js on port 3000. Requests to port 80 will get mapped to port 3000.

You should also edit your /etc/rc.local file and add that line minus the sudo. That will add the redirect when the machine boots up. You don't need sudo in /etc/rc.local because the commands there are run as root when the system boots.

Logs

Use the forever module to launch your Node.js with. It will make sure that it restarts if it ever crashes and it will redirect console logs to a file.

Launch on Boot

Add your Node.js start script to the file you edited for port redirection, /etc/rc.local. That will run your Node.js launch script when the system starts.

Digital Ocean & other VPS

This not only applies to Linode, but Digital Ocean, AWS EC2 and other VPS providers as well. However, on RedHat based systems /etc/rc.local is /ect/rc.d/local.

Community
  • 1
  • 1
Daniel
  • 35,039
  • 9
  • 83
  • 70
  • 3
    Thanks for that answer, nice and to the point. – Robotbugs May 15 '13 at 21:49
  • 21
    BTW, on Ubuntu, it is /etc/rc.local – kehers Oct 14 '13 at 07:14
  • You are awesome, works like a charm. – Victor Jan 25 '14 at 18:23
  • 12
    Often the "-i eth0" flag will be an issue for virtual private servers. Replace eth0 as needed. – JHAWN Apr 07 '14 at 02:15
  • 7
    If I add my Node.js start script to `/etc/rc.local`, won't it be executed as `root` on system boot? That would defeat the purpose of the port 80 redirect. – jamix Jun 11 '14 at 12:44
  • 1
    Yes, this is true. If you start your node.js script from `/etc/rc.local` it will run as `root`. However, security best practices are to never run a server as `root`. – Daniel Jun 12 '14 at 17:32
  • But I run webserver on port 80, so the webserver wont work in this case. Please advise. – moderns Jul 18 '14 at 12:45
  • If you have a web server already running on port 80 then you'll need to configure it to be a reverse proxy for the Node.js backend server. – Daniel Jul 19 '14 at 19:34
  • This works but not alongside my VPN (which I host on the same VPS). – davegallant Jul 22 '14 at 01:56
  • 2
    What do you mean by `add Node.js script to /etc/rc.local` ? Do I simply need to add a line that calls the script, i.e. line : `node server.js` ? – Sprout Coder Oct 08 '14 at 15:01
  • 4
    Note that for the port redirect to work, the destination port needs to be opne on your firewall as well. WRT starting a node instance on boot, we simply make use of the distributions's init scripts/systemd files that allow you to specify a user. – bk138 Dec 06 '14 at 22:28
  • Let's say that I have two ip available, one which is bound to apache on port 80, how would I add the other one to the command above? – jonathancardoso Feb 09 '15 at 20:07
  • 1
    @JCM you would change `-i eth0` to `-i eth1`. That would target the second interface instead of the first interface. – Daniel Feb 10 '15 at 18:14
  • @Daniel what if the ip is inside a sub interface (eth0:cp1)? – jonathancardoso Feb 10 '15 at 20:05
  • @JCM I haven't actually used it with a sub interface, but I suspect it would work the same way. – Daniel Feb 12 '15 at 19:48
  • @jamix & OP: Is there a solve for the rc.local command running as root? – ndmweb Apr 30 '15 at 22:28
  • 1
    @ndmweb Before we switched our Node.js app to Elastic Beanstalk where this is taken care of for us, we used `process.setgid()` and `process.setuid()` in the app itself to downgrade its permissions to those of a specially created `nodejs` system user. – jamix May 01 '15 at 07:04
  • 1
    @Daniel, The sockets sent to :80 does not seems to get forwarded to :3000 ? Can you help please ? – Sahan May 02 '15 at 02:17
  • Thanks! -from the Phoenix/Elixir world – Arthur Collé Jun 25 '15 at 02:52
  • Make sure you get the `-i` correct, or remove it – MFARID Oct 07 '15 at 14:42
  • 1
    what do you mean by add Node.js script to /etc/rc.local can you please tell me what should i wriye and also i i am using forever also ?? – Sudhanshu Gaur Mar 04 '16 at 19:45
  • @Daniel when rerouting, does *ufw* also need to allow the specificed port? Given your example, I've noticed this only works when 3000 is allowed via the firewall (`ufw allow 3000/tcp`), but that also allows anyone to access via that port; so an external person could get to the page by domainname.com (defaults to 80), domainname.com:80, domainname.com:3000, and ip address (e.g, 123.456.789.012:80/3000). I thought the desire was to allow TCP traffic on 80 to be served internally by port 3000; so all ports closed to external users except 80. – vol7ron Mar 13 '16 at 19:51
  • @bk138 I think I just repeated what you previously stated -- is that correct? Is it possible to do port forwarding internally? – vol7ron Mar 13 '16 at 19:59
  • 1
    If I use this solution and later I want to remove this redirection of traffic, then how to do it? – Naveen Attri Oct 24 '16 at 21:41
  • fantastic, seccond line of the solution worked like a charm on one of my servers. Thanks! – Pedro Emilio Borrego Rached Dec 03 '16 at 12:23
  • Instead of adding the iptables changes to rc.local, how about editing `/etc/network/interfaces`, adding these two lines: ```pre-up iptables-restore < /etc/iptables.rules post-down iptables-save > /etc/iptables.rules``` – Atomox Mar 08 '17 at 22:24
  • On a wireless laptop, can one leave out "-i eth0"? I tried this, but still doesn't work: `iptables -F iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 5000` – Marius May 08 '17 at 19:54
  • Rock on! That was perfect! Works like a charm! – tonejac Jun 14 '17 at 06:10
  • how do i undo this command? – Shreyas Jul 20 '17 at 04:36
  • 3
    very bad solution, you are loading your kernel with unnecessary traffic. all the packets are copied from one buffer to another, you are wasting resources. much better option is to setuid 0 to node process at the initialization so it can acquire privileged port, after bind() you just drop privileges. – Nulik Oct 25 '17 at 00:30
  • If you are on AWS and using the ELB, just forward the ELB ports to the correct instance ports. This solution wouldn't work if you have multiple applications running on different ports. – zed Oct 11 '18 at 11:59
  • Small point, on at least AWS: in the `/etc/rc.local` file there's the line `Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure that this script will be executed during boot.` I had the line in, but hadn't run the `chmod` command and it didn't update when the machine was relaunched – divillysausages May 03 '19 at 09:26
119

Give Safe User Permission To Use Port 80

Remember, we do NOT want to run your applications as the root user, but there is a hitch: your safe user does not have permission to use the default HTTP port (80). You goal is to be able to publish a website that visitors can use by navigating to an easy to use URL like http://ip:port/

Unfortunately, unless you sign on as root, you’ll normally have to use a URL like http://ip:port - where port number > 1024.

A lot of people get stuck here, but the solution is easy. There a few options but this is the one I like. Type the following commands:

sudo apt-get install libcap2-bin
sudo setcap cap_net_bind_service=+ep `readlink -f \`which node\``

Now, when you tell a Node application that you want it to run on port 80, it will not complain.

Check this reference link

DJCrashdummy
  • 173
  • 1
  • 12
Meet Mehta
  • 4,369
  • 3
  • 18
  • 26
  • 9
    This is the better, simpler answer. – Kyle Chadha Jan 06 '15 at 18:35
  • 2
    Also, added detailed answer here http://stackoverflow.com/questions/23281895/node-js-eacces-error-when-listening-on-http-80-port-permission-denied/23281904#23281904 – Meet Mehta Jul 06 '15 at 20:37
  • 1
    How does a web server like NGINX run on port 80? Does it do something similar? – Eric Andrew Lewis Apr 08 '16 at 18:32
  • 1
    @EricAndrewLewis: I will say it depends. This error will show up when you are running the server in non-root mode. What if you are running the Nginx server as root user! Also, if running as normal user and getting error. Run above commands to give safe permissions to access the port. Also refer to http://stackoverflow.com/questions/31369480/vagrant-and-nginx-only-works-on-ports-other-than-80 – Meet Mehta Apr 08 '16 at 20:04
16

Drop root privileges after you bind to port 80 (or 443).

This allows port 80/443 to remain protected, while still preventing you from serving requests as root:

function drop_root() {
    process.setgid('nobody');
    process.setuid('nobody');
}

A full working example using the above function:

var process = require('process');
var http = require('http');
var server = http.createServer(function(req, res) {
    res.write("Success!");
    res.end();
});

server.listen(80, null, null, function() {
    console.log('User ID:',process.getuid()+', Group ID:',process.getgid());
    drop_root();
    console.log('User ID:',process.getuid()+', Group ID:',process.getgid());
});

See more details at this full reference.

slund
  • 836
  • 8
  • 8
9

For port 80 (which was the original question), Daniel is exactly right. I recently moved to https and had to switch from iptables to a light nginx proxy managing the SSL certs. I found a useful answer along with a gist by gabrielhpugliese on how to handle that. Basically I

Hopefully that can save someone else some headaches. I'm sure there's a pure-node way of doing this, but nginx was quick and it worked.

Community
  • 1
  • 1
Nick Benes
  • 1,239
  • 15
  • 12